@plurnk/plurnk-service 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.defaults +20 -0
- package/INSTALL.md +14 -16
- package/README.md +1 -1
- package/SPEC.md +635 -296
- package/digest-sql/channel-dispositions/dispositions.sql +2 -2
- package/digest-sql/entry-dispositions/dispositions.sql +2 -2
- package/dist/Paths.d.ts +1 -2
- package/dist/Paths.d.ts.map +1 -1
- package/dist/Paths.js +1 -9
- package/dist/Paths.js.map +1 -1
- package/dist/build-info.json +1 -1
- package/dist/core/ChannelWrite.d.ts +8 -3
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +40 -8
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +17 -4
- package/dist/core/CoreSchemeServices.d.ts +7 -3
- package/dist/core/CoreSchemeServices.d.ts.map +1 -1
- package/dist/core/CoreSchemeServices.js +8 -1
- package/dist/core/CoreSchemeServices.js.map +1 -1
- package/dist/core/Dispatcher.d.ts +9 -1
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +321 -221
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/Engine.d.ts +13 -4
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +44 -22
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +101 -81
- package/dist/core/EntryAddressBinding.d.ts +32 -0
- package/dist/core/EntryAddressBinding.d.ts.map +1 -0
- package/dist/core/EntryAddressBinding.js +93 -0
- package/dist/core/EntryAddressBinding.js.map +1 -0
- package/dist/core/ExecutorRegistry.d.ts +4 -4
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +28 -28
- package/dist/core/ExecutorRegistry.js.map +1 -1
- package/dist/core/LogBody.d.ts.map +1 -1
- package/dist/core/LogBody.js +19 -1
- package/dist/core/LogBody.js.map +1 -1
- package/dist/core/OperatorConfig.d.ts.map +1 -1
- package/dist/core/OperatorConfig.js +5 -2
- package/dist/core/OperatorConfig.js.map +1 -1
- package/dist/core/OverflowTurn.d.ts.map +1 -1
- package/dist/core/OverflowTurn.js +5 -1
- package/dist/core/OverflowTurn.js.map +1 -1
- package/dist/core/Owner.js +1 -1
- package/dist/core/Owner.js.map +1 -1
- package/dist/core/PacketBuilder.d.ts +5 -1
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +21 -11
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/ProposalLifecycle.d.ts +4 -1
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +85 -26
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ReasoningEvent.d.ts +13 -0
- package/dist/core/ReasoningEvent.d.ts.map +1 -0
- package/dist/core/ReasoningEvent.js +6 -0
- package/dist/core/ReasoningEvent.js.map +1 -0
- package/dist/core/ResourceMutations.d.ts +15 -19
- package/dist/core/ResourceMutations.d.ts.map +1 -1
- package/dist/core/ResourceMutations.js +113 -38
- package/dist/core/ResourceMutations.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts +12 -11
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +52 -47
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/ToolResources.d.ts.map +1 -1
- package/dist/core/ToolResources.js +20 -19
- package/dist/core/ToolResources.js.map +1 -1
- package/dist/core/Turn.d.ts +1 -1
- package/dist/core/Turn.d.ts.map +1 -1
- package/dist/core/TurnOps.d.ts +2 -2
- package/dist/core/TurnOps.d.ts.map +1 -1
- package/dist/core/TurnOps.js +14 -4
- package/dist/core/TurnOps.js.map +1 -1
- package/dist/core/TurnRunner.d.ts +46 -3
- package/dist/core/TurnRunner.d.ts.map +1 -1
- package/dist/core/TurnRunner.js +165 -78
- package/dist/core/TurnRunner.js.map +1 -1
- package/dist/core/WorkerName.d.ts +1 -0
- package/dist/core/WorkerName.d.ts.map +1 -1
- package/dist/core/WorkerName.js +2 -1
- package/dist/core/WorkerName.js.map +1 -1
- package/dist/core/WorkerName.sql +17 -2
- package/dist/core/WorkspaceGate.js +1 -1
- package/dist/core/WorkspaceGate.js.map +1 -1
- package/dist/core/caps/CapsResolve.d.ts +2 -2
- package/dist/core/caps/CapsResolve.d.ts.map +1 -1
- package/dist/core/caps/CapsResolve.js +5 -7
- package/dist/core/caps/CapsResolve.js.map +1 -1
- package/dist/core/caps/DbChannelCaps.d.ts +1 -1
- package/dist/core/caps/DbChannelCaps.d.ts.map +1 -1
- package/dist/core/caps/DbChannelCaps.js +13 -11
- package/dist/core/caps/DbChannelCaps.js.map +1 -1
- package/dist/core/caps/DbEntryCaps.d.ts +5 -5
- package/dist/core/caps/DbEntryCaps.d.ts.map +1 -1
- package/dist/core/caps/DbEntryCaps.js +27 -30
- package/dist/core/caps/DbEntryCaps.js.map +1 -1
- package/dist/core/caps/DbNotifyCaps.d.ts +1 -1
- package/dist/core/caps/DbNotifyCaps.d.ts.map +1 -1
- package/dist/core/caps/DbNotifyCaps.js +5 -3
- package/dist/core/caps/DbNotifyCaps.js.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.d.ts +3 -3
- package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.js +16 -26
- package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts +6 -4
- package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.js +37 -5
- package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
- package/dist/core/file-creation-policy.d.ts +9 -0
- package/dist/core/file-creation-policy.d.ts.map +1 -0
- package/dist/core/file-creation-policy.js +25 -0
- package/dist/core/file-creation-policy.js.map +1 -0
- package/dist/core/fork.d.ts +4 -1
- package/dist/core/fork.d.ts.map +1 -1
- package/dist/core/fork.js +27 -15
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +35 -22
- package/dist/core/git-membership.d.ts +19 -1
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +225 -50
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +28 -19
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plurnk-uri.d.ts +13 -2
- package/dist/core/plurnk-uri.d.ts.map +1 -1
- package/dist/core/plurnk-uri.js +59 -21
- package/dist/core/plurnk-uri.js.map +1 -1
- package/dist/core/scheme-types.d.ts +1 -0
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/worker-ops.sql +10 -6
- package/dist/core/worker-settings.d.ts +1 -0
- package/dist/core/worker-settings.d.ts.map +1 -1
- package/dist/core/worker-settings.js +7 -0
- package/dist/core/worker-settings.js.map +1 -1
- package/dist/core/workspace-settings.d.ts +2 -0
- package/dist/core/workspace-settings.d.ts.map +1 -1
- package/dist/core/workspace-settings.js +5 -1
- package/dist/core/workspace-settings.js.map +1 -1
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +1 -1
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/digest.sql +1 -1
- package/dist/schemes/Exec.d.ts +3 -2
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +55 -22
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
- package/dist/schemes/ExecOutputScheme.js +5 -4
- package/dist/schemes/ExecOutputScheme.js.map +1 -1
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +170 -80
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Prompt.d.ts +2 -3
- package/dist/schemes/Prompt.d.ts.map +1 -1
- package/dist/schemes/Prompt.js +4 -7
- package/dist/schemes/Prompt.js.map +1 -1
- package/dist/schemes/Skill.d.ts.map +1 -1
- package/dist/schemes/Skill.js +3 -1
- package/dist/schemes/Skill.js.map +1 -1
- package/dist/schemes/Worker.d.ts.map +1 -1
- package/dist/schemes/Worker.js +95 -49
- package/dist/schemes/Worker.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts +5 -5
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +22 -17
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +58 -23
- package/dist/schemes/_entry-find.d.ts +3 -2
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +26 -25
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-find.sql +10 -7
- package/dist/schemes/_entry-manifest.d.ts +2 -2
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +6 -5
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts +6 -5
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +39 -46
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-ops.sql +9 -6
- package/dist/schemes/_entry-send.d.ts +2 -2
- package/dist/schemes/_entry-send.d.ts.map +1 -1
- package/dist/schemes/_entry-send.js +16 -14
- package/dist/schemes/_entry-send.js.map +1 -1
- package/dist/schemes/_search-index.d.ts.map +1 -1
- package/dist/schemes/_search-index.js +2 -0
- package/dist/schemes/_search-index.js.map +1 -1
- package/dist/server/Daemon.d.ts +42 -10
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +326 -248
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/DaemonModule.d.ts +84 -19
- package/dist/server/DaemonModule.d.ts.map +1 -1
- package/dist/server/DrainSupervisor.d.ts +3 -1
- package/dist/server/DrainSupervisor.d.ts.map +1 -1
- package/dist/server/DrainSupervisor.js +11 -9
- package/dist/server/DrainSupervisor.js.map +1 -1
- package/dist/server/Functionality.d.ts +44 -0
- package/dist/server/Functionality.d.ts.map +1 -0
- package/dist/server/Functionality.js +422 -0
- package/dist/server/Functionality.js.map +1 -0
- package/dist/server/FunctionalityManager.d.ts +22 -0
- package/dist/server/FunctionalityManager.d.ts.map +1 -0
- package/dist/server/FunctionalityManager.js +121 -0
- package/dist/server/FunctionalityManager.js.map +1 -0
- package/dist/server/SkillsFunctionality.d.ts +61 -0
- package/dist/server/SkillsFunctionality.d.ts.map +1 -0
- package/dist/server/SkillsFunctionality.js +449 -0
- package/dist/server/SkillsFunctionality.js.map +1 -0
- package/dist/server/WorkerCapabilities.d.ts +21 -0
- package/dist/server/WorkerCapabilities.d.ts.map +1 -0
- package/dist/server/WorkerCapabilities.js +214 -0
- package/dist/server/WorkerCapabilities.js.map +1 -0
- package/dist/server/WorkerResidency.d.ts +40 -0
- package/dist/server/WorkerResidency.d.ts.map +1 -0
- package/dist/server/WorkerResidency.js +283 -0
- package/dist/server/WorkerResidency.js.map +1 -0
- package/dist/server/client-input.d.ts +1 -0
- package/dist/server/client-input.d.ts.map +1 -1
- package/dist/server/client-input.js +22 -1
- package/dist/server/client-input.js.map +1 -1
- package/dist/server/dispatch-as-plurnk.d.ts +2 -2
- package/dist/server/dispatch-as-plurnk.d.ts.map +1 -1
- package/dist/server/dispatch-as-plurnk.js +89 -25
- package/dist/server/dispatch-as-plurnk.js.map +1 -1
- package/dist/server/drain.sql +11 -6
- package/dist/server/envelope.d.ts +6 -1
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +8 -2
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/envelope.sql +11 -3
- package/dist/server/lifecycle-recovery.sql +13 -16
- package/dist/server/loopDocs.d.ts +2 -1
- package/dist/server/loopDocs.d.ts.map +1 -1
- package/dist/server/loopDocs.js +67 -51
- package/dist/server/loopDocs.js.map +1 -1
- package/dist/server/loopDocs.sql +14 -10
- package/dist/server/module-discovery.d.ts +2 -2
- package/dist/server/module-discovery.d.ts.map +1 -1
- package/dist/server/module-discovery.js +27 -2
- package/dist/server/module-discovery.js.map +1 -1
- package/dist/server/seam-entry-read.sql +9 -7
- package/dist/server/seam-log-read.sql +1 -1
- package/dist/server/seam-loop.sql +10 -1
- package/dist/server/seam-proposal-list.sql +4 -2
- package/dist/server/worker-capabilities.sql +19 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +11 -1
- package/dist/service.js.map +1 -1
- package/migrations/001_schema.sql +486 -136
- package/package.json +34 -32
- package/dist/server/skillDocs.d.ts +0 -9
- package/dist/server/skillDocs.d.ts.map +0 -1
- package/dist/server/skillDocs.js +0 -212
- package/dist/server/skillDocs.js.map +0 -1
- package/dist/server/skillDocs.sql +0 -14
- package/dist/server/workspace-capabilities.sql +0 -19
package/dist/server/Daemon.js
CHANGED
|
@@ -19,11 +19,11 @@ import LogEntry from "./logEntry.js";
|
|
|
19
19
|
import Envelope from "./envelope.js";
|
|
20
20
|
import ClientInput from "./client-input.js";
|
|
21
21
|
import Turn from "../core/Turn.js";
|
|
22
|
-
import
|
|
23
|
-
import SkillDocs from "./skillDocs.js";
|
|
22
|
+
import SkillsFunctionality from "./SkillsFunctionality.js";
|
|
24
23
|
import GitMembership from "../core/git-membership.js";
|
|
25
24
|
import Fork from "../core/fork.js";
|
|
26
25
|
import WorkerName from "../core/WorkerName.js";
|
|
26
|
+
import WorkerControlAddress from "../core/WorkerControlAddress.js";
|
|
27
27
|
import LoopLifecycle from "../core/LoopLifecycle.js";
|
|
28
28
|
import { promptLoopPrefix } from "../core/plurnk-uri.js";
|
|
29
29
|
import { contentWeight } from "../core/content-weight.js";
|
|
@@ -33,7 +33,9 @@ import { resolveLoopRoute } from "./loop-model.js";
|
|
|
33
33
|
import LoopFlagsReader from "../core/LoopFlagsReader.js";
|
|
34
34
|
import Results, { OperationFailureError } from "../core/results.js";
|
|
35
35
|
import WorkspaceGate from "../core/WorkspaceGate.js";
|
|
36
|
+
import WorkerResidency from "./WorkerResidency.js";
|
|
36
37
|
import BranchBatches from "./BranchBatches.js";
|
|
38
|
+
import Functionality from "./Functionality.js";
|
|
37
39
|
import { observed, observedSync } from "../observe/spans.js";
|
|
38
40
|
import { listModelCatalog } from "./model-catalog.js";
|
|
39
41
|
const clientActionFailure = (error) => {
|
|
@@ -64,16 +66,17 @@ export default class Daemon {
|
|
|
64
66
|
#nodeModulesPath;
|
|
65
67
|
#discoveryCwd;
|
|
66
68
|
#started = false; // {§module-lifecycle}: one discovery/module boot; no listener
|
|
67
|
-
#capabilitiesPublished = false;
|
|
68
69
|
#modules = [];
|
|
69
70
|
#moduleClosers = [];
|
|
70
71
|
#moduleActions = new Map();
|
|
71
|
-
#
|
|
72
|
+
#residency;
|
|
73
|
+
#functionality;
|
|
74
|
+
#skills;
|
|
72
75
|
// {§methods-event-subscribe} — the broadcast's in-process event source. A transport
|
|
73
76
|
// module (plurnk-agui) subscribes and fans out to its OWN clients; core emits, never owns
|
|
74
77
|
// client transport or connection state.
|
|
75
78
|
#eventSubscribers = new Set();
|
|
76
|
-
constructor({ db, schemes, mimetypes, provider, nodeModulesPath, }) {
|
|
79
|
+
constructor({ db, schemes, mimetypes, provider, nodeModulesPath, skills, }) {
|
|
77
80
|
this.#db = db;
|
|
78
81
|
this.#lifecycle = new LoopLifecycle(db);
|
|
79
82
|
this.#schemes = schemes ?? new SchemeRegistry();
|
|
@@ -104,6 +107,26 @@ export default class Daemon {
|
|
|
104
107
|
});
|
|
105
108
|
return row !== undefined;
|
|
106
109
|
});
|
|
110
|
+
// {§module-worker-residency} — residency, activation/cooling, and
|
|
111
|
+
// capability replacement live in their one owner; the Daemon delegates.
|
|
112
|
+
this.#residency = new WorkerResidency({
|
|
113
|
+
db,
|
|
114
|
+
engine: () => this.#engine,
|
|
115
|
+
workspaceGate: this.#workspaceGate,
|
|
116
|
+
normalizeRuntime: (registration) => this.#normalizeRuntime(registration),
|
|
117
|
+
});
|
|
118
|
+
// {§functionality-coordinator} — the one owner above every family
|
|
119
|
+
// adapter; Core hosts it, modules register adapters through the seam.
|
|
120
|
+
this.#functionality = new Functionality({
|
|
121
|
+
registerModuleAction: (registration) => this.registerModuleAction(registration),
|
|
122
|
+
registerWorkerCapabilityProvider: (owner, provider) => this.registerWorkerCapabilityProvider(owner, provider),
|
|
123
|
+
readWorkerModuleState: (workerId, owner) => this.readWorkerModuleState(workerId, owner),
|
|
124
|
+
replaceWorkerCapabilities: (replacement, options) => this.replaceWorkerCapabilities(replacement, options),
|
|
125
|
+
retainWorker: (workerId) => this.#residency.retain(workerId),
|
|
126
|
+
});
|
|
127
|
+
// {§skills-functionality} — Core's own family: standard Agent Skills.
|
|
128
|
+
this.#skills = new SkillsFunctionality({ db, ...skills });
|
|
129
|
+
this.#skills.attach(this.#functionality.register(this.#skills));
|
|
107
130
|
this.#branchBatches = new BranchBatches(db, this.#workspaceGate, {
|
|
108
131
|
settleWorkspace: async (workspaceId) => this.#engine.drainWorkspaceDerivations(workspaceId),
|
|
109
132
|
createChild: async ({ workspaceId, parentWorkerId, parentLoopId, op, name, prompt, flags, origin }) => {
|
|
@@ -111,12 +134,13 @@ export default class Daemon {
|
|
|
111
134
|
const providerSpec = parentPolicy.childProviderSpec ?? parentPolicy.providerSpec;
|
|
112
135
|
const workerName = WorkerName.assert(name);
|
|
113
136
|
const workerId = op === "FORK"
|
|
114
|
-
? await Fork.fork(this.#db, parentWorkerId, workerName)
|
|
137
|
+
? await Fork.fork(this.#db, parentWorkerId, workerName, (scheme) => this.#schemes.entryInheritanceForStoredScheme(scheme, parentWorkerId))
|
|
115
138
|
: (await this.#db.fork_insert_worker.get({
|
|
116
139
|
workspace_id: workspaceId,
|
|
117
140
|
name: workerName,
|
|
118
141
|
parent_worker_id: parentWorkerId,
|
|
119
142
|
origin,
|
|
143
|
+
fork_snapshot: 0,
|
|
120
144
|
}))?.id;
|
|
121
145
|
if (workerId === undefined)
|
|
122
146
|
throw new Error("Branch worker insert returned no row");
|
|
@@ -128,9 +152,11 @@ export default class Daemon {
|
|
|
128
152
|
spawn_model_route_id: null,
|
|
129
153
|
reasoning_policy: parentPolicy.reasoningPolicy,
|
|
130
154
|
});
|
|
155
|
+
const source = await this.#workerPromptSource(workspaceId, parentWorkerId, workerId);
|
|
131
156
|
const loopId = await this.#drains.enqueueFreshLoop({
|
|
132
157
|
workerId,
|
|
133
158
|
prompt,
|
|
159
|
+
...(source === undefined ? {} : { source }),
|
|
134
160
|
providerSpec,
|
|
135
161
|
reasoningPolicy: parentPolicy.reasoningPolicy,
|
|
136
162
|
childProviderSpec: parentPolicy.childProviderSpec,
|
|
@@ -164,13 +190,16 @@ export default class Daemon {
|
|
|
164
190
|
// model-independent. Request-shaped token facts stay provider-owned.
|
|
165
191
|
weigh: contentWeight,
|
|
166
192
|
streamEventNotify: (workspaceId, event) => this.notifyStreamEvent(workspaceId, event),
|
|
193
|
+
reasoningEventNotify: (workspaceId, event) => this.notifyReasoningEvent(workspaceId, event),
|
|
167
194
|
wakeWorkerNotify: (payload) => this.#drains.notifyWakeWorker(payload),
|
|
168
195
|
// worker:// loop-start primitive — spawn/fork/irc deliver through
|
|
169
196
|
// Daemon.inject (active sister → fold; idle → enqueue + drain). The
|
|
170
197
|
// daemon owns provider + the law-file system prompt; the worker scheme
|
|
171
198
|
// handler carries neither. Fire-and-forget: the returned drain runs
|
|
172
199
|
// independently (the sister is its own worker). {§machine-processes}
|
|
173
|
-
injectWorker: async ({ workspaceId, workerId, prompt, flags, parentLoopId }) => {
|
|
200
|
+
injectWorker: async ({ workspaceId, workerId, sourceWorkerId, prompt, flags, parentLoopId }) => {
|
|
201
|
+
await this.#assertModelWorker(workspaceId, workerId);
|
|
202
|
+
const source = await this.#workerPromptSource(workspaceId, sourceWorkerId, workerId);
|
|
174
203
|
const systemPrompt = await readFile(Paths.instructionsSystem, "utf8");
|
|
175
204
|
let providerSpec;
|
|
176
205
|
let childProviderSpec;
|
|
@@ -207,6 +236,7 @@ export default class Daemon {
|
|
|
207
236
|
workspaceId,
|
|
208
237
|
workerId,
|
|
209
238
|
prompt,
|
|
239
|
+
...(source === undefined ? {} : { source }),
|
|
210
240
|
providerSpec,
|
|
211
241
|
reasoningPolicy,
|
|
212
242
|
childProviderSpec,
|
|
@@ -218,11 +248,11 @@ export default class Daemon {
|
|
|
218
248
|
branchWorker: async (args) => this.#branchBatches.enqueue(args),
|
|
219
249
|
branchCompletionGate: async (workerId) => this.#branchBatches.completionGate(workerId),
|
|
220
250
|
acquireWorkspaceTurn: async (workspaceId, workerId) => this.#workspaceGate.acquireTurn(workspaceId, workerId),
|
|
221
|
-
// {§skills-
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
workspaceTurnStarting: async ({ workspaceId }) => {
|
|
225
|
-
await
|
|
251
|
+
// {§skills-hotload} — filesystem installers operate out of band.
|
|
252
|
+
// Republish under the workspace turn gate before packet assembly so
|
|
253
|
+
// the first subsequent model turn sees their exact result.
|
|
254
|
+
workspaceTurnStarting: async ({ workspaceId, workerId }) => {
|
|
255
|
+
await this.#skills.refreshIfChanged({ workspaceId, workerId });
|
|
226
256
|
},
|
|
227
257
|
workspaceTurnCompleted: async ({ turnId }) => {
|
|
228
258
|
await this.#branchBatches.sealTurn(turnId);
|
|
@@ -236,7 +266,7 @@ export default class Daemon {
|
|
|
236
266
|
this.#drains = new DrainSupervisor({
|
|
237
267
|
db,
|
|
238
268
|
lifecycle: this.#lifecycle,
|
|
239
|
-
injectPrompt: (workerId, prompt, openPaths) => this.#engine.inject(workerId, prompt, openPaths),
|
|
269
|
+
injectPrompt: (workerId, prompt, openPaths, source) => this.#engine.inject(workerId, prompt, openPaths, source),
|
|
240
270
|
assertInjectionCompatibility: async ({ workerId, loopId, providerSpec, providerSpecExplicit, reasoningPolicy, childProviderSpec, turnCeiling, flags, }) => {
|
|
241
271
|
await this.#assertFoldPosture(workerId, flags, loopId);
|
|
242
272
|
// An omitted selector keeps the loop's durable provider; only an
|
|
@@ -252,21 +282,34 @@ export default class Daemon {
|
|
|
252
282
|
},
|
|
253
283
|
reconcilePrompts: (workerId, endedLoopId) => this.#reconcileOrphanedPrompts(workerId, endedLoopId),
|
|
254
284
|
runLoop: async ({ workspaceId, workerId, loopId, maxTurns, prompt, systemPrompt, signal, onDispatch, }) => {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
285
|
+
// The drain callback is the final provider/model boundary. Every
|
|
286
|
+
// admission path, including boot recovery, terminates here.
|
|
287
|
+
await this.#assertModelWorker(workspaceId, workerId);
|
|
288
|
+
const releaseCapabilities = await this.#residency.acquire(workspaceId, workerId);
|
|
289
|
+
try {
|
|
290
|
+
// Worker settings can change while Functionality remains
|
|
291
|
+
// resident. Reconcile the worker-private discovery surface
|
|
292
|
+
// at the same pre-inference boundary used for activation.
|
|
293
|
+
await this.#residency.reconcile(workspaceId, workerId);
|
|
294
|
+
const { provider, childProvider } = await this.#providersForLoop(loopId);
|
|
295
|
+
return await this.#engine.runLoop({
|
|
296
|
+
provider,
|
|
297
|
+
childProvider,
|
|
298
|
+
workspaceId,
|
|
299
|
+
workerId,
|
|
300
|
+
loopId,
|
|
301
|
+
maxTurns,
|
|
302
|
+
messages: [
|
|
303
|
+
{ role: "system", content: systemPrompt },
|
|
304
|
+
{ role: "user", content: prompt },
|
|
305
|
+
],
|
|
306
|
+
signal,
|
|
307
|
+
onDispatch,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
finally {
|
|
311
|
+
releaseCapabilities();
|
|
312
|
+
}
|
|
270
313
|
},
|
|
271
314
|
loopUsage: (loopId) => this.#engine.loopUsage(loopId),
|
|
272
315
|
loopAttributions: (loopId) => this.#engine.loopAttributions(loopId),
|
|
@@ -333,7 +376,9 @@ export default class Daemon {
|
|
|
333
376
|
async runLoop(args) {
|
|
334
377
|
const workspaceId = ClientInput.assertId("runLoop", "workspaceId", args.workspaceId);
|
|
335
378
|
const workerId = ClientInput.assertId("runLoop", "workerId", args.workerId);
|
|
379
|
+
await this.#assertModelWorker(workspaceId, workerId);
|
|
336
380
|
const prompt = ClientInput.assertPrompt("runLoop", args.prompt);
|
|
381
|
+
const source = ClientInput.assertOptionalSource("runLoop", args.source);
|
|
337
382
|
const requestedMaxTurns = ClientInput.assertMaxTurns("runLoop", args.maxTurns);
|
|
338
383
|
const openPaths = ClientInput.assertOpenPaths("runLoop", args.openPaths);
|
|
339
384
|
const selector = ClientInput.assertOptionalSelector("runLoop", "selector", args.selector);
|
|
@@ -356,28 +401,6 @@ export default class Daemon {
|
|
|
356
401
|
// {§methods-loop-run-child-provider} — the worker's persistent spawn override.
|
|
357
402
|
const childSelection = await this.#resolveWorkerSpawnModel(workerId, childSelector);
|
|
358
403
|
const systemPrompt = await readFile(Paths.instructionsSystem, "utf8");
|
|
359
|
-
// {§machine-processes} — the model NEVER runs in a client-origin worker (its packets would carry
|
|
360
|
-
// client-action rows). The module resolves the model worker via ensureModelWorker and passes it (or a
|
|
361
|
-
// fork); a client worker here is a caller error, refused loudly rather than silently rehomed.
|
|
362
|
-
const target = await this.#db.envelope_get_worker_by_id.get({ id: workerId });
|
|
363
|
-
if (target === undefined) {
|
|
364
|
-
throw daemonFailure("daemon:worker", "worker-not-found", 404, `Worker ${workerId} does not exist.`, { workerId });
|
|
365
|
-
}
|
|
366
|
-
if (target.workspace_id !== workspaceId) {
|
|
367
|
-
throw daemonFailure("daemon:worker", "workspace-mismatch", 409, `Worker ${workerId} does not belong to workspace ${workspaceId}.`, {
|
|
368
|
-
workerId,
|
|
369
|
-
workspaceId,
|
|
370
|
-
actualWorkspaceId: target.workspace_id,
|
|
371
|
-
retryable: false,
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
if (target.origin === "client") {
|
|
375
|
-
throw daemonFailure("daemon:worker", "model-worker-required", 409, `Worker ${workerId} is not a model worker.`, {
|
|
376
|
-
workerId,
|
|
377
|
-
recovery: "Select or create a model worker for this loop.",
|
|
378
|
-
retryable: false,
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
404
|
// {§operator-config-max-turns-ceiling} — the operator ceiling clamps a per-call maxTurns; a
|
|
382
405
|
// seam caller must not bypass operator policy (inject only DEFAULTS from env, never clamps).
|
|
383
406
|
const ceiling = Number(process.env.PLURNK_SERVICE_MAX_TURNS ?? "-1");
|
|
@@ -391,6 +414,7 @@ export default class Daemon {
|
|
|
391
414
|
workspaceId,
|
|
392
415
|
workerId,
|
|
393
416
|
prompt,
|
|
417
|
+
...(source === undefined ? {} : { source }),
|
|
394
418
|
...(flags !== undefined ? { flags } : {}),
|
|
395
419
|
...(openPaths !== undefined ? { openPaths } : {}),
|
|
396
420
|
turnCeiling,
|
|
@@ -819,24 +843,71 @@ export default class Daemon {
|
|
|
819
843
|
throw daemonFailure("daemon:worker", "workspace-mismatch", 409, `Worker ${workerId} does not belong to workspace ${workspaceId}.`, { workerId, workspaceId, actualWorkspaceId: owner.workspace_id });
|
|
820
844
|
}
|
|
821
845
|
}
|
|
846
|
+
// {§machine-processes} — provider inference is an exclusive capability of a
|
|
847
|
+
// model worker. The same assertion is used by early client admissions and
|
|
848
|
+
// the final drain callback, so recovery and internal callers cannot bypass it.
|
|
849
|
+
async #assertModelWorker(workspaceId, workerId) {
|
|
850
|
+
const worker = await this.#db.envelope_get_worker_by_id.get({ id: workerId });
|
|
851
|
+
if (worker === undefined) {
|
|
852
|
+
throw daemonFailure("daemon:worker", "worker-not-found", 404, `Worker ${workerId} does not exist.`, { workerId });
|
|
853
|
+
}
|
|
854
|
+
if (worker.workspace_id !== workspaceId) {
|
|
855
|
+
throw daemonFailure("daemon:worker", "workspace-mismatch", 409, `Worker ${workerId} does not belong to workspace ${workspaceId}.`, {
|
|
856
|
+
workerId,
|
|
857
|
+
workspaceId,
|
|
858
|
+
actualWorkspaceId: worker.workspace_id,
|
|
859
|
+
retryable: false,
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
if (worker.origin !== "model") {
|
|
863
|
+
throw daemonFailure("daemon:worker", "model-worker-required", 409, `Worker ${workerId} is not a model worker.`, {
|
|
864
|
+
workerId,
|
|
865
|
+
actualOrigin: worker.origin,
|
|
866
|
+
recovery: "Select or create a model worker for this loop.",
|
|
867
|
+
retryable: false,
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
async #workerPromptSource(workspaceId, sourceWorkerId, targetWorkerId) {
|
|
872
|
+
if (sourceWorkerId === targetWorkerId)
|
|
873
|
+
return undefined;
|
|
874
|
+
const source = await this.#db.envelope_get_worker_by_id.get({ id: sourceWorkerId });
|
|
875
|
+
if (source === undefined || source.workspace_id !== workspaceId) {
|
|
876
|
+
throw new Error(`Worker prompt source ${sourceWorkerId} does not belong to workspace ${workspaceId}.`);
|
|
877
|
+
}
|
|
878
|
+
return WorkerControlAddress.render(source.name);
|
|
879
|
+
}
|
|
822
880
|
// {§methods-op-mirror} — execute parsed ops on behalf of a client as a
|
|
823
881
|
// client-origin turn (the log is core's, a client op is a first-class citizen), dispatched through
|
|
824
882
|
// the engine, then emitted as log/entry on the event source. One seam op backs the whole op_*
|
|
825
883
|
// family (read/edit/copy/find/fold/look/move/open/send/exec); the module parses at its edge with the
|
|
826
884
|
// grammar package and hands over the statement, then fans the emitted entry out to its own clients.
|
|
885
|
+
// {§functionality-model-mutation} — await every queued Functionality
|
|
886
|
+
// publication (a Worker's accepted mutation publishes at its turn boundary).
|
|
887
|
+
async settleFunctionality(workerId) {
|
|
888
|
+
await this.#functionality.settle(workerId);
|
|
889
|
+
}
|
|
827
890
|
async dispatchAsClient(args) {
|
|
828
891
|
const workspaceId = ClientInput.assertId("operation.dispatch", "workspaceId", args.workspaceId);
|
|
829
892
|
const workerId = ClientInput.assertId("operation.dispatch", "workerId", args.workerId);
|
|
893
|
+
const functionalityWorkerId = ClientInput.assertId("operation.dispatch", "functionalityWorkerId", args.functionalityWorkerId);
|
|
830
894
|
const { statement } = args;
|
|
831
|
-
|
|
895
|
+
await this.#assertWorkerOwned(workspaceId, functionalityWorkerId);
|
|
896
|
+
const releaseCapabilities = await this.#residency.acquire(workspaceId, functionalityWorkerId);
|
|
832
897
|
try {
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
|
|
898
|
+
const clientLoopId = await Envelope.ensureClientLoop(this.#db, workerId);
|
|
899
|
+
try {
|
|
900
|
+
const result = await this.#dispatchClientStatement({ workspaceId, workerId, functionalityWorkerId, loopId: clientLoopId, statement });
|
|
901
|
+
await Envelope.closeClientLoop(this.#db, clientLoopId, { status: 200 });
|
|
902
|
+
return result;
|
|
903
|
+
}
|
|
904
|
+
catch (error) {
|
|
905
|
+
await Envelope.closeClientLoop(this.#db, clientLoopId, clientActionFailure(error));
|
|
906
|
+
throw error;
|
|
907
|
+
}
|
|
836
908
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
throw error;
|
|
909
|
+
finally {
|
|
910
|
+
releaseCapabilities();
|
|
840
911
|
}
|
|
841
912
|
}
|
|
842
913
|
// The client-interface action contract: one AG-UI action owns one administrative
|
|
@@ -846,25 +917,35 @@ export default class Daemon {
|
|
|
846
917
|
async dispatchClientAction(args) {
|
|
847
918
|
const workspaceId = ClientInput.assertId("operation.dispatch-batch", "workspaceId", args.workspaceId);
|
|
848
919
|
const workerId = ClientInput.assertId("operation.dispatch-batch", "workerId", args.workerId);
|
|
920
|
+
const functionalityWorkerId = ClientInput.assertId("operation.dispatch-batch", "functionalityWorkerId", args.functionalityWorkerId);
|
|
849
921
|
const { statements } = args;
|
|
850
922
|
if (statements.length === 0)
|
|
851
923
|
return [];
|
|
852
|
-
|
|
924
|
+
await this.#assertWorkerOwned(workspaceId, functionalityWorkerId);
|
|
925
|
+
// {§actor-boundary-attached-functionality} — the attached Worker's
|
|
926
|
+
// Functionality is what the client operation executes in.
|
|
927
|
+
const releaseCapabilities = await this.#residency.acquire(workspaceId, functionalityWorkerId);
|
|
853
928
|
try {
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
results
|
|
929
|
+
const clientLoopId = await Envelope.ensureClientLoop(this.#db, workerId);
|
|
930
|
+
try {
|
|
931
|
+
const results = [];
|
|
932
|
+
for (const statement of statements) {
|
|
933
|
+
results.push(await this.#dispatchClientStatement({ workspaceId, workerId, functionalityWorkerId, loopId: clientLoopId, statement }));
|
|
934
|
+
}
|
|
935
|
+
await Envelope.closeClientLoop(this.#db, clientLoopId, { status: 200 });
|
|
936
|
+
return results;
|
|
937
|
+
}
|
|
938
|
+
catch (error) {
|
|
939
|
+
await Envelope.closeClientLoop(this.#db, clientLoopId, clientActionFailure(error));
|
|
940
|
+
throw error;
|
|
857
941
|
}
|
|
858
|
-
await Envelope.closeClientLoop(this.#db, clientLoopId, { status: 200 });
|
|
859
|
-
return results;
|
|
860
942
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
throw error;
|
|
943
|
+
finally {
|
|
944
|
+
releaseCapabilities();
|
|
864
945
|
}
|
|
865
946
|
}
|
|
866
947
|
async #dispatchClientStatement(args) {
|
|
867
|
-
const { workspaceId, workerId, loopId, statement } = args;
|
|
948
|
+
const { workspaceId, workerId, functionalityWorkerId, loopId, statement } = args;
|
|
868
949
|
const release = await this.#workspaceGate.acquireTurn(workspaceId, workerId);
|
|
869
950
|
try {
|
|
870
951
|
const { id: turnId } = await Turn.open(this.#db, {
|
|
@@ -876,7 +957,7 @@ export default class Daemon {
|
|
|
876
957
|
try {
|
|
877
958
|
const entryIds = [];
|
|
878
959
|
const result = await this.#engine.dispatch({
|
|
879
|
-
statement, workspaceId, workerId, loopId, turnId, sequence: 1,
|
|
960
|
+
statement, workspaceId, workerId, functionalityWorkerId, loopId, turnId, sequence: 1,
|
|
880
961
|
origin: "client", onDispatch: (logEntryId) => { entryIds.push(logEntryId); },
|
|
881
962
|
});
|
|
882
963
|
await Turn.complete(this.#db, turnId, result.status);
|
|
@@ -913,20 +994,30 @@ export default class Daemon {
|
|
|
913
994
|
async look(args) {
|
|
914
995
|
const workspaceId = ClientInput.assertId("operation.look", "workspaceId", args.workspaceId);
|
|
915
996
|
const workerId = ClientInput.assertId("operation.look", "workerId", args.workerId);
|
|
997
|
+
const functionalityWorkerId = ClientInput.assertId("operation.look", "functionalityWorkerId", args.functionalityWorkerId);
|
|
916
998
|
const { statement } = args;
|
|
917
|
-
|
|
918
|
-
const
|
|
999
|
+
await this.#assertWorkerOwned(workspaceId, functionalityWorkerId);
|
|
1000
|
+
const releaseCapabilities = await this.#residency.acquire(workspaceId, functionalityWorkerId);
|
|
919
1001
|
try {
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1002
|
+
const releaseWorkspace = await this.#workspaceGate.acquireTurn(workspaceId, workerId);
|
|
1003
|
+
try {
|
|
1004
|
+
const clientLoopId = await Envelope.ensureClientLoop(this.#db, workerId);
|
|
1005
|
+
try {
|
|
1006
|
+
const result = await this.#engine.look({ statement, workspaceId, workerId, functionalityWorkerId, loopId: clientLoopId });
|
|
1007
|
+
await Envelope.closeClientLoop(this.#db, clientLoopId, { status: 200 });
|
|
1008
|
+
return result;
|
|
1009
|
+
}
|
|
1010
|
+
catch (error) {
|
|
1011
|
+
await Envelope.closeClientLoop(this.#db, clientLoopId, clientActionFailure(error));
|
|
1012
|
+
throw error;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
finally {
|
|
1016
|
+
releaseWorkspace();
|
|
1017
|
+
}
|
|
927
1018
|
}
|
|
928
1019
|
finally {
|
|
929
|
-
|
|
1020
|
+
releaseCapabilities();
|
|
930
1021
|
}
|
|
931
1022
|
}
|
|
932
1023
|
// {§methods-log-read} — a workspace's journal, the module's primary render input. The worker is
|
|
@@ -1030,8 +1121,60 @@ export default class Daemon {
|
|
|
1030
1121
|
return Validator.assertClientDisplayCapabilities([...schemes, ...mimetypes]);
|
|
1031
1122
|
}
|
|
1032
1123
|
listWorkspaces() { return Envelope.listWorkspaces(this.#db); }
|
|
1033
|
-
listWorkers(workspaceId) {
|
|
1034
|
-
|
|
1124
|
+
listWorkers(workspaceId, query = {}) {
|
|
1125
|
+
const origin = query.origin;
|
|
1126
|
+
if (origin !== undefined && !["model", "client", "_plurnk"].includes(origin)) {
|
|
1127
|
+
throw daemonFailure("daemon:worker", "origin-invalid", 400, `Worker origin '${String(origin)}' is invalid.`, { origin, retryable: false });
|
|
1128
|
+
}
|
|
1129
|
+
const parentWorkerId = query.parentWorkerId === undefined
|
|
1130
|
+
? undefined
|
|
1131
|
+
: query.parentWorkerId === null
|
|
1132
|
+
? null
|
|
1133
|
+
: ClientInput.assertId("workspace.workers", "parentWorkerId", query.parentWorkerId);
|
|
1134
|
+
// {§methods-worker-list} — an omitted parent filter must stay absent:
|
|
1135
|
+
// the envelope keys the filter on the property's presence, not its value.
|
|
1136
|
+
return Envelope.listWorkersForWorkspace(this.#db, ClientInput.assertId("workspace.workers", "workspaceId", workspaceId), {
|
|
1137
|
+
...(origin === undefined ? {} : { origin }),
|
|
1138
|
+
...(parentWorkerId === undefined ? {} : { parentWorkerId }),
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
async readWorker(args) {
|
|
1142
|
+
const workspaceId = ClientInput.assertId("worker.read", "workspaceId", args.workspaceId);
|
|
1143
|
+
const hasId = Object.hasOwn(args.identity, "id");
|
|
1144
|
+
const hasName = Object.hasOwn(args.identity, "name");
|
|
1145
|
+
if (hasId === hasName) {
|
|
1146
|
+
throw daemonFailure("daemon:worker", "identity-invalid", 400, "Worker observation requires exactly one id or name.", { retryable: false });
|
|
1147
|
+
}
|
|
1148
|
+
const row = hasId
|
|
1149
|
+
? await this.#db.envelope_get_worker_by_id.get({
|
|
1150
|
+
id: ClientInput.assertId("worker.read", "id", args.identity.id),
|
|
1151
|
+
})
|
|
1152
|
+
: await this.#db.envelope_get_worker_by_name.get({
|
|
1153
|
+
workspace_id: workspaceId,
|
|
1154
|
+
name: ClientInput.assertOptionalWorkerName("worker.read", "name", args.identity.name),
|
|
1155
|
+
});
|
|
1156
|
+
if (row === undefined || row.workspace_id !== workspaceId)
|
|
1157
|
+
return null;
|
|
1158
|
+
const { workspace_id: _workspaceId, ...projection } = row;
|
|
1159
|
+
return projection;
|
|
1160
|
+
}
|
|
1161
|
+
async listWorkerLoops(args) {
|
|
1162
|
+
const workspaceId = ClientInput.assertId("worker.loops", "workspaceId", args.workspaceId);
|
|
1163
|
+
const workerId = ClientInput.assertId("worker.loops", "workerId", args.workerId);
|
|
1164
|
+
await this.#assertWorkerOwned(workspaceId, workerId);
|
|
1165
|
+
const rows = await this.#db.application_list_worker_loops.all({ worker_id: workerId });
|
|
1166
|
+
return rows.map((row) => ({
|
|
1167
|
+
id: row.id,
|
|
1168
|
+
workerId: row.workerId,
|
|
1169
|
+
sequence: row.sequence,
|
|
1170
|
+
status: row.status,
|
|
1171
|
+
prompt: row.prompt,
|
|
1172
|
+
promptSource: row.promptSource,
|
|
1173
|
+
terminatedAt: row.terminatedAt,
|
|
1174
|
+
terminalResult: row.terminalResult === null
|
|
1175
|
+
? null
|
|
1176
|
+
: Validator.assertOperationResult(JSON.parse(row.terminalResult)),
|
|
1177
|
+
}));
|
|
1035
1178
|
}
|
|
1036
1179
|
// {§methods-workspace-prompts}: root-conversation loop seeds, newest-first.
|
|
1037
1180
|
listPrompts(workspaceId, limit) {
|
|
@@ -1058,7 +1201,7 @@ export default class Daemon {
|
|
|
1058
1201
|
}
|
|
1059
1202
|
// {§methods-workspace-create}: the module owns protocol decoding; core validates the typed seam
|
|
1060
1203
|
// inputs and owns the envelope, its reserved-name + name-uniqueness invariants,
|
|
1061
|
-
// membership resolution
|
|
1204
|
+
// membership resolution and the workspace/created emit. No connection state
|
|
1062
1205
|
// (which client is on which workspace) lives here — that's the module's.
|
|
1063
1206
|
async createWorkspace(args) {
|
|
1064
1207
|
// The seam fails hard on malformed semantic input so every module inherits one wall:
|
|
@@ -1078,26 +1221,18 @@ export default class Daemon {
|
|
|
1078
1221
|
}
|
|
1079
1222
|
if (constraints.length > 0)
|
|
1080
1223
|
await GitMembership.resolveGitMembership(this.#db, envelope.workspaceId, undefined);
|
|
1081
|
-
for (const provider of this.#workspaceCapabilityProviders.values()) {
|
|
1082
|
-
await provider.hydrate(envelope.workspaceId);
|
|
1083
|
-
}
|
|
1084
|
-
await LoopDocs.materialize(this.#engine, this.#db, envelope.workspaceId);
|
|
1085
|
-
await SkillDocs.materialize(this.#engine, this.#db, envelope.workspaceId);
|
|
1086
|
-
void this.#engine.warmWorkspaceDerivations(envelope.workspaceId).catch(() => { });
|
|
1087
1224
|
this.#broadcast("all", "workspace/created", { id: envelope.workspaceId, name: envelope.workspaceName, projectRoot: envelope.projectRoot });
|
|
1088
1225
|
return envelope;
|
|
1089
1226
|
});
|
|
1090
1227
|
}
|
|
1091
1228
|
async attachWorkspace(args) {
|
|
1092
|
-
// attachToWorkspace owns the reserved-name + worker-ownership invariants; the seam just delegates
|
|
1229
|
+
// attachToWorkspace owns the reserved-name + worker-ownership invariants; the seam just delegates.
|
|
1093
1230
|
const workspaceId = ClientInput.assertId("workspace.attach", "workspaceId", args.workspaceId);
|
|
1094
1231
|
const workerId = args.workerId === undefined
|
|
1095
1232
|
? undefined
|
|
1096
1233
|
: ClientInput.assertId("workspace.attach", "workerId", args.workerId);
|
|
1097
1234
|
const workerName = ClientInput.assertOptionalWorkerName("workspace.attach", "workerName", args.workerName);
|
|
1098
|
-
|
|
1099
|
-
void this.#engine.warmWorkspaceDerivations(envelope.workspaceId).catch(() => { });
|
|
1100
|
-
return envelope;
|
|
1235
|
+
return Envelope.attachToWorkspace(this.#db, workspaceId, { workerId, workerName });
|
|
1101
1236
|
}
|
|
1102
1237
|
async renameWorkspace(workspaceId, name) {
|
|
1103
1238
|
const checkedWorkspaceId = ClientInput.assertId("workspace.rename", "workspaceId", workspaceId);
|
|
@@ -1116,7 +1251,7 @@ export default class Daemon {
|
|
|
1116
1251
|
// Members may have just landed — begin warming now, but return the constraint response
|
|
1117
1252
|
// immediately so prompts do not wait for the complete derivation corpus.
|
|
1118
1253
|
void this.#engine.warmWorkspaceDerivations(checkedWorkspaceId).catch(() => { });
|
|
1119
|
-
return { effect, glob };
|
|
1254
|
+
return { effect, glob, source: "explicit" };
|
|
1120
1255
|
}
|
|
1121
1256
|
finally {
|
|
1122
1257
|
release();
|
|
@@ -1163,8 +1298,10 @@ export default class Daemon {
|
|
|
1163
1298
|
retryable: false,
|
|
1164
1299
|
});
|
|
1165
1300
|
}
|
|
1166
|
-
const
|
|
1301
|
+
const releaseCapabilities = await this.#residency.acquire(workspaceId, workerId);
|
|
1302
|
+
let releaseWorkspace;
|
|
1167
1303
|
try {
|
|
1304
|
+
releaseWorkspace = await this.#workspaceGate.acquireTurn(workspaceId, workerId);
|
|
1168
1305
|
let parsed;
|
|
1169
1306
|
try {
|
|
1170
1307
|
parsed = parsePath(args.target);
|
|
@@ -1215,6 +1352,7 @@ export default class Daemon {
|
|
|
1215
1352
|
workspace_id: workspaceId,
|
|
1216
1353
|
owner_id: location.ownerId,
|
|
1217
1354
|
scheme: location.scheme,
|
|
1355
|
+
authority: location.authority,
|
|
1218
1356
|
pathname: location.pathname,
|
|
1219
1357
|
});
|
|
1220
1358
|
if (row === undefined) {
|
|
@@ -1262,7 +1400,8 @@ export default class Daemon {
|
|
|
1262
1400
|
});
|
|
1263
1401
|
}
|
|
1264
1402
|
finally {
|
|
1265
|
-
|
|
1403
|
+
releaseWorkspace?.();
|
|
1404
|
+
releaseCapabilities();
|
|
1266
1405
|
}
|
|
1267
1406
|
}
|
|
1268
1407
|
// {§methods-conversation-worker}: a fresh conversation is a model-origin root worker with an empty private log.
|
|
@@ -1289,9 +1428,9 @@ export default class Daemon {
|
|
|
1289
1428
|
// {§worker-scheme-fork} — branch a worker's log while sharing the workspace world.
|
|
1290
1429
|
// Core owns the workspace check and immutable worker-name admission.
|
|
1291
1430
|
async forkWorker(args) {
|
|
1292
|
-
const workspaceId = ClientInput.assertId("
|
|
1293
|
-
const workerId = ClientInput.assertId("
|
|
1294
|
-
const name = ClientInput.assertOptionalWorkerName("
|
|
1431
|
+
const workspaceId = ClientInput.assertId("run.fork", "workspaceId", args.workspaceId);
|
|
1432
|
+
const workerId = ClientInput.assertId("run.fork", "workerId", args.workerId);
|
|
1433
|
+
const name = ClientInput.assertOptionalWorkerName("run.fork", "name", args.name);
|
|
1295
1434
|
const owner = await this.#db.envelope_get_worker_by_id.get({ id: workerId });
|
|
1296
1435
|
if (owner === undefined) {
|
|
1297
1436
|
throw daemonFailure("daemon:worker", "worker-not-found", 404, `Worker ${workerId} does not exist.`, { workerId });
|
|
@@ -1310,32 +1449,23 @@ export default class Daemon {
|
|
|
1310
1449
|
throw daemonFailure("daemon:worker", "name-conflict", 409, `Worker name '${name}' is already in use in workspace ${workspaceId}.`, { workspaceId, name, recovery: "Choose another worker name.", retryable: false });
|
|
1311
1450
|
}
|
|
1312
1451
|
}
|
|
1313
|
-
const branchWorkerId = await Fork.fork(this.#db, workerId, name);
|
|
1452
|
+
const branchWorkerId = await Fork.fork(this.#db, workerId, name, (scheme) => this.#schemes.entryInheritanceForStoredScheme(scheme, workerId));
|
|
1314
1453
|
const branch = await this.#db.envelope_get_worker_by_id.get({ id: branchWorkerId });
|
|
1315
1454
|
return { workerId: branchWorkerId, workerName: branch?.name ?? null, parentWorkerId: workerId };
|
|
1316
1455
|
}
|
|
1317
1456
|
async registerRuntimes(registrations) {
|
|
1318
1457
|
const normalized = registrations.map((registration) => this.#normalizeRuntime(registration));
|
|
1319
1458
|
this.#engine.registerRuntimes(normalized);
|
|
1320
|
-
|
|
1321
|
-
for (const workspace of await Envelope.listWorkspaces(this.#db)) {
|
|
1322
|
-
await LoopDocs.materialize(this.#engine, this.#db, workspace.id);
|
|
1323
|
-
await SkillDocs.materialize(this.#engine, this.#db, workspace.id);
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1459
|
+
await this.#residency.rematerializeActive();
|
|
1326
1460
|
}
|
|
1327
1461
|
async registerRuntime(registration) {
|
|
1328
1462
|
await this.registerRuntimes([registration]);
|
|
1329
1463
|
}
|
|
1330
1464
|
async registerScheme(name, handler) {
|
|
1331
1465
|
this.#schemes.register(name, handler);
|
|
1332
|
-
if (this.#
|
|
1466
|
+
if (this.#residency.published)
|
|
1333
1467
|
await this.#schemes.ready();
|
|
1334
|
-
|
|
1335
|
-
await LoopDocs.materialize(this.#engine, this.#db, workspace.id);
|
|
1336
|
-
await SkillDocs.materialize(this.#engine, this.#db, workspace.id);
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1468
|
+
await this.#residency.rematerializeActive();
|
|
1339
1469
|
}
|
|
1340
1470
|
#normalizeRuntime({ namespaceOwner, decl, executor, availability, scheme }) {
|
|
1341
1471
|
if (typeof namespaceOwner !== "string" || namespaceOwner.trim().length === 0) {
|
|
@@ -1360,12 +1490,26 @@ export default class Daemon {
|
|
|
1360
1490
|
};
|
|
1361
1491
|
}
|
|
1362
1492
|
registerModuleAction(registration) {
|
|
1363
|
-
const { name, scope, handler } = registration;
|
|
1493
|
+
const { name, scope, inputSchema, outputSchema, handler } = registration;
|
|
1364
1494
|
if (name.length === 0)
|
|
1365
1495
|
throw new Error("registerModuleAction: action name must not be empty");
|
|
1366
|
-
if (scope !== "worldless" && scope !== "workspace") {
|
|
1496
|
+
if (scope !== "worldless" && scope !== "workspace" && scope !== "worker") {
|
|
1367
1497
|
throw new Error(`module action '${name}' has invalid scope '${String(scope)}'`);
|
|
1368
1498
|
}
|
|
1499
|
+
if (inputSchema === null || typeof inputSchema !== "object" || Array.isArray(inputSchema)) {
|
|
1500
|
+
throw new Error(`module action '${name}' has no input schema`);
|
|
1501
|
+
}
|
|
1502
|
+
if (outputSchema === null || typeof outputSchema !== "object" || Array.isArray(outputSchema)) {
|
|
1503
|
+
throw new Error(`module action '${name}' has no output schema`);
|
|
1504
|
+
}
|
|
1505
|
+
for (const [role, schema] of [["input", inputSchema], ["output", outputSchema]]) {
|
|
1506
|
+
try {
|
|
1507
|
+
Validator.validateJsonSchemaInstance(schema, {});
|
|
1508
|
+
}
|
|
1509
|
+
catch (cause) {
|
|
1510
|
+
throw new Error(`module action '${name}' has an invalid ${role} schema`, { cause });
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1369
1513
|
if (typeof handler !== "function")
|
|
1370
1514
|
throw new Error(`module action '${name}' has no handler`);
|
|
1371
1515
|
if (this.#moduleActions.has(name))
|
|
@@ -1374,7 +1518,12 @@ export default class Daemon {
|
|
|
1374
1518
|
}
|
|
1375
1519
|
listModuleActions() {
|
|
1376
1520
|
return [...this.#moduleActions.values()]
|
|
1377
|
-
.map(({ name, scope }) => ({
|
|
1521
|
+
.map(({ name, scope, inputSchema, outputSchema }) => ({
|
|
1522
|
+
name,
|
|
1523
|
+
scope,
|
|
1524
|
+
inputSchema,
|
|
1525
|
+
outputSchema,
|
|
1526
|
+
}))
|
|
1378
1527
|
.toSorted((left, right) => left.name.localeCompare(right.name));
|
|
1379
1528
|
}
|
|
1380
1529
|
async invokeModuleAction(name, params, context) {
|
|
@@ -1384,131 +1533,38 @@ export default class Daemon {
|
|
|
1384
1533
|
if (registration.scope !== context.scope) {
|
|
1385
1534
|
throw new Error(`module action '${name}' requires ${registration.scope} context, not ${context.scope}`);
|
|
1386
1535
|
}
|
|
1536
|
+
let releaseCapabilities;
|
|
1387
1537
|
if (context.scope === "workspace") {
|
|
1388
1538
|
ClientInput.assertId(`module action '${name}'`, "workspaceId", context.workspaceId);
|
|
1389
1539
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
if (this.#workspaceCapabilityProviders.has(namespaceOwner)) {
|
|
1397
|
-
throw new Error(`workspace capability provider '${namespaceOwner}' is already registered`);
|
|
1398
|
-
}
|
|
1399
|
-
this.#workspaceCapabilityProviders.set(namespaceOwner, provider);
|
|
1400
|
-
}
|
|
1401
|
-
async readWorkspaceModuleState(workspaceId, namespaceOwner) {
|
|
1402
|
-
const checkedWorkspaceId = ClientInput.assertId("workspace module state", "workspaceId", workspaceId);
|
|
1403
|
-
if (namespaceOwner.trim().length === 0) {
|
|
1404
|
-
throw new Error("workspace module state requires a non-empty namespace owner");
|
|
1405
|
-
}
|
|
1406
|
-
const row = await this.#db.workspace_module_state_get.get({
|
|
1407
|
-
workspace_id: checkedWorkspaceId,
|
|
1408
|
-
namespace_owner: namespaceOwner,
|
|
1409
|
-
});
|
|
1410
|
-
return row === undefined ? null : JSON.parse(row.state);
|
|
1411
|
-
}
|
|
1412
|
-
async replaceWorkspaceCapabilities({ workspaceId, namespaceOwner, state, runtimes, }) {
|
|
1413
|
-
const checkedWorkspaceId = ClientInput.assertId("workspace capability replacement", "workspaceId", workspaceId);
|
|
1414
|
-
if (namespaceOwner.trim().length === 0) {
|
|
1415
|
-
throw new Error("workspace capability replacement requires a non-empty namespace owner");
|
|
1416
|
-
}
|
|
1417
|
-
const workspace = await this.#db.envelope_get_workspace.get({ id: checkedWorkspaceId });
|
|
1418
|
-
if (workspace === undefined) {
|
|
1419
|
-
throw daemonFailure("daemon:workspace-capability", "workspace-not-found", 404, `Workspace ${checkedWorkspaceId} does not exist.`, { workspaceId: checkedWorkspaceId, retryable: false });
|
|
1420
|
-
}
|
|
1421
|
-
const encoded = state === null ? null : JSON.stringify(state);
|
|
1422
|
-
if (state !== null && encoded === undefined) {
|
|
1423
|
-
throw daemonFailure("daemon:workspace-capability", "state-not-json", 400, "Workspace module state is not JSON-serializable.", { namespaceOwner, retryable: false });
|
|
1424
|
-
}
|
|
1425
|
-
if (encoded !== null)
|
|
1426
|
-
JSON.parse(encoded);
|
|
1427
|
-
const normalized = runtimes.map((registration) => {
|
|
1428
|
-
if (registration.namespaceOwner !== namespaceOwner) {
|
|
1429
|
-
throw new Error(`workspace runtime owner '${registration.namespaceOwner}' does not match '${namespaceOwner}'`);
|
|
1540
|
+
else if (context.scope === "worker") {
|
|
1541
|
+
const workspaceId = ClientInput.assertId(`module action '${name}'`, "workspaceId", context.workspaceId);
|
|
1542
|
+
const workerId = ClientInput.assertId(`module action '${name}'`, "workerId", context.workerId);
|
|
1543
|
+
const worker = await this.#db.envelope_get_worker_by_id.get({ id: workerId });
|
|
1544
|
+
if (worker === undefined || worker.workspace_id !== workspaceId) {
|
|
1545
|
+
throw daemonFailure("daemon:module-action", "worker-not-found", 404, `Worker ${workerId} does not exist in workspace ${workspaceId}.`, { workspaceId, workerId, retryable: false });
|
|
1430
1546
|
}
|
|
1431
|
-
|
|
1432
|
-
});
|
|
1433
|
-
const gate = this.#workspaceGate.tryExclusive(checkedWorkspaceId);
|
|
1434
|
-
if (gate === null) {
|
|
1435
|
-
throw daemonFailure("daemon:workspace-capability", "workspace-busy", 409, `Workspace ${checkedWorkspaceId} is running an operation or another capability change.`, {
|
|
1436
|
-
workspaceId: checkedWorkspaceId,
|
|
1437
|
-
namespaceOwner,
|
|
1438
|
-
recovery: "Settle the current operation and retry the capability change.",
|
|
1439
|
-
retryable: true,
|
|
1440
|
-
});
|
|
1547
|
+
releaseCapabilities = await this.#residency.acquire(workspaceId, workerId);
|
|
1441
1548
|
}
|
|
1442
|
-
await gate.acquired;
|
|
1443
|
-
const prior = await this.#db.workspace_module_state_get.get({
|
|
1444
|
-
workspace_id: checkedWorkspaceId,
|
|
1445
|
-
namespace_owner: namespaceOwner,
|
|
1446
|
-
});
|
|
1447
|
-
let rollbackRuntimes;
|
|
1448
|
-
let stateChanged = false;
|
|
1449
1549
|
try {
|
|
1450
|
-
|
|
1451
|
-
if (encoded === null) {
|
|
1452
|
-
await this.#db.workspace_module_state_delete.run({
|
|
1453
|
-
workspace_id: checkedWorkspaceId,
|
|
1454
|
-
namespace_owner: namespaceOwner,
|
|
1455
|
-
});
|
|
1456
|
-
}
|
|
1457
|
-
else {
|
|
1458
|
-
await this.#db.workspace_module_state_put.run({
|
|
1459
|
-
workspace_id: checkedWorkspaceId,
|
|
1460
|
-
namespace_owner: namespaceOwner,
|
|
1461
|
-
state: encoded,
|
|
1462
|
-
});
|
|
1463
|
-
}
|
|
1464
|
-
stateChanged = true;
|
|
1465
|
-
rollbackRuntimes = commitRuntimes();
|
|
1466
|
-
if (this.#capabilitiesPublished) {
|
|
1467
|
-
await LoopDocs.materialize(this.#engine, this.#db, checkedWorkspaceId);
|
|
1468
|
-
await SkillDocs.materialize(this.#engine, this.#db, checkedWorkspaceId);
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
catch (cause) {
|
|
1472
|
-
rollbackRuntimes?.();
|
|
1473
|
-
const rollbackErrors = [];
|
|
1474
|
-
if (stateChanged) {
|
|
1475
|
-
try {
|
|
1476
|
-
if (prior === undefined) {
|
|
1477
|
-
await this.#db.workspace_module_state_delete.run({
|
|
1478
|
-
workspace_id: checkedWorkspaceId,
|
|
1479
|
-
namespace_owner: namespaceOwner,
|
|
1480
|
-
});
|
|
1481
|
-
}
|
|
1482
|
-
else {
|
|
1483
|
-
await this.#db.workspace_module_state_put.run({
|
|
1484
|
-
workspace_id: checkedWorkspaceId,
|
|
1485
|
-
namespace_owner: namespaceOwner,
|
|
1486
|
-
state: prior.state,
|
|
1487
|
-
});
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
catch (rollbackCause) {
|
|
1491
|
-
rollbackErrors.push(rollbackCause);
|
|
1492
|
-
}
|
|
1493
|
-
if (this.#capabilitiesPublished) {
|
|
1494
|
-
try {
|
|
1495
|
-
await LoopDocs.materialize(this.#engine, this.#db, checkedWorkspaceId);
|
|
1496
|
-
await SkillDocs.materialize(this.#engine, this.#db, checkedWorkspaceId);
|
|
1497
|
-
}
|
|
1498
|
-
catch (rollbackCause) {
|
|
1499
|
-
rollbackErrors.push(rollbackCause);
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
if (rollbackErrors.length > 0) {
|
|
1504
|
-
throw new AggregateError([cause, ...rollbackErrors], "Workspace capability replacement and rollback failed");
|
|
1505
|
-
}
|
|
1506
|
-
throw cause;
|
|
1550
|
+
return await registration.handler(params, context);
|
|
1507
1551
|
}
|
|
1508
1552
|
finally {
|
|
1509
|
-
|
|
1553
|
+
releaseCapabilities?.();
|
|
1510
1554
|
}
|
|
1511
1555
|
}
|
|
1556
|
+
registerWorkerCapabilityProvider(namespaceOwner, provider) {
|
|
1557
|
+
this.#residency.registerProvider(namespaceOwner, provider);
|
|
1558
|
+
}
|
|
1559
|
+
registerFunctionalityAdapter(adapter) {
|
|
1560
|
+
return this.#functionality.register(adapter);
|
|
1561
|
+
}
|
|
1562
|
+
async readWorkerModuleState(workerId, namespaceOwner) {
|
|
1563
|
+
return this.#residency.readModuleState(workerId, namespaceOwner);
|
|
1564
|
+
}
|
|
1565
|
+
async replaceWorkerCapabilities(replacement, options = {}) {
|
|
1566
|
+
await this.#residency.replace(replacement, options);
|
|
1567
|
+
}
|
|
1512
1568
|
get engine() { return this.#engine; }
|
|
1513
1569
|
get provider() { return this.#provider; }
|
|
1514
1570
|
get schemes() { return this.#schemes; }
|
|
@@ -1534,6 +1590,7 @@ export default class Daemon {
|
|
|
1534
1590
|
// shell is the default runtime, so its executor must boot usable.
|
|
1535
1591
|
const executors = await ExecutorRegistry.build({ defaultRuntime: "sh", cwd: this.#discoveryCwd });
|
|
1536
1592
|
this.#engine.setExecutors(executors);
|
|
1593
|
+
this.#engine.setFunctionalityDocuments((workerId) => this.#functionality.documents(workerId));
|
|
1537
1594
|
// {§question-tool} — the native request-user-input runtime, process-wide;
|
|
1538
1595
|
// per-worker admission gates its doc visibility and dispatch ({§worker-settings}).
|
|
1539
1596
|
await this.#engine.registerRuntimes([{
|
|
@@ -1574,20 +1631,8 @@ export default class Daemon {
|
|
|
1574
1631
|
this.#moduleClosers.push(module);
|
|
1575
1632
|
await module.setup?.(setupSeam);
|
|
1576
1633
|
}
|
|
1577
|
-
for (const workspace of await Envelope.listWorkspaces(this.#db)) {
|
|
1578
|
-
for (const provider of this.#workspaceCapabilityProviders.values()) {
|
|
1579
|
-
await provider.hydrate(workspace.id);
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
1634
|
await this.#schemes.ready();
|
|
1583
|
-
|
|
1584
|
-
// Installed capabilities and operator configuration are now fully known; model loops
|
|
1585
|
-
// consume this workspace state but never republish it.
|
|
1586
|
-
for (const workspace of await Envelope.listWorkspaces(this.#db)) {
|
|
1587
|
-
await LoopDocs.materialize(this.#engine, this.#db, workspace.id);
|
|
1588
|
-
await SkillDocs.materialize(this.#engine, this.#db, workspace.id);
|
|
1589
|
-
}
|
|
1590
|
-
this.#capabilitiesPublished = true;
|
|
1635
|
+
this.#residency.publish();
|
|
1591
1636
|
await this.#recoverLifecycle();
|
|
1592
1637
|
// {§module-lifecycle} — the daemon opens no transport. Modules start their listeners only
|
|
1593
1638
|
// after capability publication and durable lifecycle recovery are complete.
|
|
@@ -1610,11 +1655,24 @@ export default class Daemon {
|
|
|
1610
1655
|
await this.#db.recovery_resume_unblocked_parks.run({});
|
|
1611
1656
|
await this.#branchBatches.recover();
|
|
1612
1657
|
const orphanSources = await this.#db.recovery_orphan_prompt_sources.all({});
|
|
1658
|
+
const invalidOrphan = orphanSources.find(({ origin }) => origin !== "model");
|
|
1659
|
+
if (invalidOrphan !== undefined) {
|
|
1660
|
+
throw new Error(`Orphaned provider work belongs to ${invalidOrphan.origin} worker ${invalidOrphan.worker_id}; only model workers may enter inference.`);
|
|
1661
|
+
}
|
|
1613
1662
|
for (const source of orphanSources) {
|
|
1614
1663
|
await this.#drains.reconcileOrphanedPrompts(source.worker_id, source.loop_id);
|
|
1615
1664
|
}
|
|
1616
1665
|
const systemPrompt = await readFile(Paths.instructionsSystem, "utf8");
|
|
1617
1666
|
const queued = await this.#db.recovery_queued_workers.all({});
|
|
1667
|
+
const invalidQueued = queued.find(({ origin }) => origin !== "model");
|
|
1668
|
+
if (invalidQueued !== undefined) {
|
|
1669
|
+
throw new Error(`Queued provider work belongs to ${invalidQueued.origin} worker ${invalidQueued.worker_id}; only model workers may enter inference.`);
|
|
1670
|
+
}
|
|
1671
|
+
const parked = await this.#db.recovery_parked_workers.all({});
|
|
1672
|
+
const invalidParked = parked.find(({ origin }) => origin !== "model");
|
|
1673
|
+
if (invalidParked !== undefined) {
|
|
1674
|
+
throw new Error(`Parked provider work belongs to ${invalidParked.origin} worker ${invalidParked.worker_id}; only model workers may enter inference.`);
|
|
1675
|
+
}
|
|
1618
1676
|
for (const row of queued) {
|
|
1619
1677
|
const started = await this.#drains.ensureDrain({
|
|
1620
1678
|
workspaceId: row.workspace_id,
|
|
@@ -1622,10 +1680,11 @@ export default class Daemon {
|
|
|
1622
1680
|
systemPrompt,
|
|
1623
1681
|
});
|
|
1624
1682
|
started?.drainPromise.catch((err) => {
|
|
1625
|
-
|
|
1683
|
+
if (this.#started) {
|
|
1684
|
+
console.error(`recovered drain failed for worker ${row.worker_id}:`, err);
|
|
1685
|
+
}
|
|
1626
1686
|
});
|
|
1627
1687
|
}
|
|
1628
|
-
const parked = await this.#db.recovery_parked_workers.all({});
|
|
1629
1688
|
for (const row of parked) {
|
|
1630
1689
|
await this.#drains.schedulePollWake(row.workspace_id, row.worker_id, systemPrompt);
|
|
1631
1690
|
}
|
|
@@ -1644,6 +1703,15 @@ export default class Daemon {
|
|
|
1644
1703
|
if (!this.#started)
|
|
1645
1704
|
return;
|
|
1646
1705
|
this.#started = false;
|
|
1706
|
+
// Stop every producer before refusing capability acquisition. A drain
|
|
1707
|
+
// already between queue claim and activation must observe its own abort,
|
|
1708
|
+
// not misclassify orderly shutdown as a capability failure.
|
|
1709
|
+
const derivationAbort = new DOMException("daemon stopping", "AbortError");
|
|
1710
|
+
this.#engine.cancelAllProposals("daemon_stopping");
|
|
1711
|
+
this.#engine.cancelDerivations(derivationAbort);
|
|
1712
|
+
this.#branchBatches.beginStop();
|
|
1713
|
+
this.#drains.beginStop("daemon_stopping");
|
|
1714
|
+
this.#residency.beginStop();
|
|
1647
1715
|
const stopDeadlineMs = Daemon.#stopDeadlineMs();
|
|
1648
1716
|
const deadline = Date.now() + stopDeadlineMs;
|
|
1649
1717
|
const settle = (label, wait) => new Promise((resolve) => {
|
|
@@ -1677,11 +1745,6 @@ export default class Daemon {
|
|
|
1677
1745
|
// Settle the stopped world FIRST: a drain paused at a pending proposal awaits a resolution
|
|
1678
1746
|
// that will never arrive once clients are gone — allSettled(drains) below would deadlock
|
|
1679
1747
|
// the stop forever (a daemon with a pending HITL proposal could not shut down).
|
|
1680
|
-
const derivationAbort = new DOMException("daemon stopping", "AbortError");
|
|
1681
|
-
this.#engine.cancelAllProposals("daemon_stopping");
|
|
1682
|
-
this.#engine.cancelDerivations(derivationAbort);
|
|
1683
|
-
this.#branchBatches.beginStop();
|
|
1684
|
-
this.#drains.beginStop("daemon_stopping");
|
|
1685
1748
|
// {§crash-only-stop} — the settle sequence is DEADLINE-BOUNDED: a child
|
|
1686
1749
|
// that never closes (a wedged MCP server, a stuck stream) must not hang
|
|
1687
1750
|
// the daemon forever. Past the deadline the waits are abandoned; the
|
|
@@ -1689,6 +1752,9 @@ export default class Daemon {
|
|
|
1689
1752
|
// leak as a live-but-wedged tree.
|
|
1690
1753
|
const branchResult = await settle("branch batches idle", () => this.#branchBatches.idle());
|
|
1691
1754
|
const drainResult = await settle("drains idle", () => this.#drains.idle());
|
|
1755
|
+
// A boundary publication queued behind the last turn settles before
|
|
1756
|
+
// modules close and before the DB goes away ({§functionality-publication}).
|
|
1757
|
+
const functionalityResult = await settle("functionality publications", () => this.#functionality.settle());
|
|
1692
1758
|
const moduleResult = await settle("modules close", async () => {
|
|
1693
1759
|
const results = await moduleClose;
|
|
1694
1760
|
if (results.some((r) => r.status === "rejected"))
|
|
@@ -1706,6 +1772,7 @@ export default class Daemon {
|
|
|
1706
1772
|
const wakeResult = await settle("drains idle (wake)", () => this.#drains.idle());
|
|
1707
1773
|
const closeErrors = [
|
|
1708
1774
|
moduleResult,
|
|
1775
|
+
functionalityResult,
|
|
1709
1776
|
branchResult,
|
|
1710
1777
|
drainResult,
|
|
1711
1778
|
streamingResult,
|
|
@@ -1736,6 +1803,10 @@ export default class Daemon {
|
|
|
1736
1803
|
notifyStreamEvent(workspaceId, event) {
|
|
1737
1804
|
this.#broadcast({ workspaceId }, "stream/event", event);
|
|
1738
1805
|
}
|
|
1806
|
+
/** Emit transient readable reasoning for an in-flight model call. */
|
|
1807
|
+
notifyReasoningEvent(workspaceId, event) {
|
|
1808
|
+
this.#broadcast({ workspaceId }, "reasoning/event", event);
|
|
1809
|
+
}
|
|
1739
1810
|
/**
|
|
1740
1811
|
* Emit a transient notice scoped to the workspace containing the loop.
|
|
1741
1812
|
*/
|
|
@@ -1781,6 +1852,7 @@ export default class Daemon {
|
|
|
1781
1852
|
worker_id: workerId,
|
|
1782
1853
|
sequence: seqRow.next,
|
|
1783
1854
|
prompt: first.body,
|
|
1855
|
+
prompt_source: first.prompt_source,
|
|
1784
1856
|
flags: first.flags,
|
|
1785
1857
|
model_route_id: first.model_route_id,
|
|
1786
1858
|
spawn_model_route_id: first.spawn_model_route_id,
|
|
@@ -1807,6 +1879,12 @@ export default class Daemon {
|
|
|
1807
1879
|
cancelDrain(workerId, reason = "user_cancelled") {
|
|
1808
1880
|
return this.#drains.cancel(workerId, reason);
|
|
1809
1881
|
}
|
|
1882
|
+
async cancelWorker(args) {
|
|
1883
|
+
const workspaceId = ClientInput.assertId("worker.cancel", "workspaceId", args.workspaceId);
|
|
1884
|
+
const workerId = ClientInput.assertId("worker.cancel", "workerId", args.workerId);
|
|
1885
|
+
await this.#assertWorkerOwned(workspaceId, workerId);
|
|
1886
|
+
await this.#drains.cancelWorkerTree(workerId, args.reason ?? "user_cancelled");
|
|
1887
|
+
}
|
|
1810
1888
|
// Process-local stream activity is supplied to the drain owner through the
|
|
1811
1889
|
// scheme registry rather than exposing that registry across the boundary.
|
|
1812
1890
|
#workerHasActiveStreams(workerId) {
|