@plurnk/plurnk-service 1.6.1 → 1.8.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 +29 -42
- package/INSTALL.md +49 -10
- package/README.md +11 -1
- package/SPEC.md +702 -287
- package/digest-sql/channel-dispositions/dispositions.sql +15 -0
- package/digest-sql/channel-state/state.sql +6 -0
- package/digest-sql/curation/curation.sql +5 -0
- package/digest-sql/derivations/derivations.sql +4 -0
- package/digest-sql/embeddings/embeddings.sql +4 -0
- package/digest-sql/entry-dispositions/dispositions.sql +15 -0
- package/digest-sql/entry-state/state.sql +6 -0
- package/digest-sql/token-counts/token-counts.sql +2 -0
- package/dist/Paths.d.ts +2 -5
- package/dist/Paths.d.ts.map +1 -1
- package/dist/Paths.js +9 -21
- package/dist/Paths.js.map +1 -1
- package/dist/build-info.json +1 -1
- package/dist/content/read-projector.d.ts.map +1 -1
- package/dist/content/read-projector.js +4 -3
- package/dist/content/read-projector.js.map +1 -1
- package/dist/core/BudgetReadout.d.ts.map +1 -1
- package/dist/core/BudgetReadout.js +12 -22
- package/dist/core/BudgetReadout.js.map +1 -1
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +2 -1
- package/dist/core/ClientInteractions.d.ts +19 -0
- package/dist/core/ClientInteractions.d.ts.map +1 -0
- package/dist/core/ClientInteractions.js +124 -0
- package/dist/core/ClientInteractions.js.map +1 -0
- package/dist/core/ClientInteractions.sql +34 -0
- package/dist/core/CoreSchemeServices.d.ts +8 -2
- package/dist/core/CoreSchemeServices.d.ts.map +1 -1
- package/dist/core/CoreSchemeServices.js +7 -1
- package/dist/core/CoreSchemeServices.js.map +1 -1
- package/dist/core/Dispatcher.d.ts +9 -6
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +74 -125
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/Engine.d.ts +26 -9
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +125 -44
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +112 -107
- package/dist/core/ExecutorRegistry.d.ts +19 -5
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +144 -18
- package/dist/core/ExecutorRegistry.js.map +1 -1
- package/dist/core/HostPaths.d.ts +29 -0
- package/dist/core/HostPaths.d.ts.map +1 -0
- package/dist/core/HostPaths.js +74 -0
- package/dist/core/HostPaths.js.map +1 -0
- package/dist/core/LegacyHome.d.ts +10 -0
- package/dist/core/LegacyHome.d.ts.map +1 -0
- package/dist/core/LegacyHome.js +170 -0
- package/dist/core/LegacyHome.js.map +1 -0
- package/dist/core/LogBody.d.ts +1 -1
- package/dist/core/LogBody.d.ts.map +1 -1
- package/dist/core/LogBody.js +2 -2
- package/dist/core/LogBody.js.map +1 -1
- package/dist/core/LogEntryProjection.js +1 -1
- package/dist/core/LogEntryProjection.js.map +1 -1
- package/dist/core/LogVisibility.d.ts +28 -0
- package/dist/core/LogVisibility.d.ts.map +1 -0
- package/dist/core/LogVisibility.js +215 -0
- package/dist/core/LogVisibility.js.map +1 -0
- package/dist/core/LoopLifecycle.d.ts +1 -0
- package/dist/core/LoopLifecycle.d.ts.map +1 -1
- package/dist/core/LoopLifecycle.js +8 -0
- package/dist/core/LoopLifecycle.js.map +1 -1
- package/dist/core/LoopLifecycle.sql +10 -0
- package/dist/core/OperatorConfig.d.ts +6 -0
- package/dist/core/OperatorConfig.d.ts.map +1 -0
- package/dist/core/OperatorConfig.js +92 -0
- package/dist/core/OperatorConfig.js.map +1 -0
- package/dist/core/OverflowTurn.d.ts +11 -0
- package/dist/core/OverflowTurn.d.ts.map +1 -0
- package/dist/core/OverflowTurn.js +77 -0
- package/dist/core/OverflowTurn.js.map +1 -0
- package/dist/core/Owner.js +2 -2
- package/dist/core/Owner.js.map +1 -1
- package/dist/core/PacketBuilder.d.ts +3 -15
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +52 -113
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/ProblemLog.js +1 -1
- package/dist/core/ProblemLog.js.map +1 -1
- package/dist/core/ProposalLifecycle.d.ts +3 -1
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +9 -26
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts +9 -5
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +99 -104
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/ResourceMutations.d.ts +2 -2
- package/dist/core/ResourceMutations.d.ts.map +1 -1
- package/dist/core/ResourceMutations.js +13 -11
- package/dist/core/ResourceMutations.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts +18 -11
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +201 -53
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/ToolResources.d.ts +19 -0
- package/dist/core/ToolResources.d.ts.map +1 -0
- package/dist/core/ToolResources.js +122 -0
- package/dist/core/ToolResources.js.map +1 -0
- package/dist/core/Turn.d.ts +27 -0
- package/dist/core/Turn.d.ts.map +1 -0
- package/dist/core/Turn.js +43 -0
- package/dist/core/Turn.js.map +1 -0
- package/dist/core/Turn.sql +72 -0
- package/dist/core/TurnOps.d.ts +7 -0
- package/dist/core/TurnOps.d.ts.map +1 -0
- package/dist/core/TurnOps.js +63 -0
- package/dist/core/TurnOps.js.map +1 -0
- package/dist/core/TurnRunner.d.ts +15 -5
- package/dist/core/TurnRunner.d.ts.map +1 -1
- package/dist/core/TurnRunner.js +1050 -900
- package/dist/core/TurnRunner.js.map +1 -1
- package/dist/core/WorkerName.d.ts +1 -1
- package/dist/core/WorkerName.d.ts.map +1 -1
- package/dist/core/WorkspaceGate.d.ts +1 -0
- package/dist/core/WorkspaceGate.d.ts.map +1 -1
- package/dist/core/WorkspaceGate.js +39 -0
- package/dist/core/WorkspaceGate.js.map +1 -1
- package/dist/core/caps/CoreInteractionCaps.d.ts +9 -0
- package/dist/core/caps/CoreInteractionCaps.d.ts.map +1 -0
- package/dist/core/caps/CoreInteractionCaps.js +14 -0
- package/dist/core/caps/CoreInteractionCaps.js.map +1 -0
- package/dist/core/caps/SchemeCtxImpl.d.ts +2 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.js +3 -0
- package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
- package/dist/core/env-defaults.d.ts.map +1 -1
- package/dist/core/env-defaults.js +7 -5
- package/dist/core/env-defaults.js.map +1 -1
- package/dist/core/fork.d.ts.map +1 -1
- package/dist/core/fork.js +15 -4
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +57 -27
- package/dist/core/git-env.d.ts +0 -1
- package/dist/core/git-env.d.ts.map +1 -1
- package/dist/core/git-env.js +0 -3
- package/dist/core/git-env.js.map +1 -1
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +10 -26
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/git-state.d.ts.map +1 -1
- package/dist/core/git-state.js +13 -14
- package/dist/core/git-state.js.map +1 -1
- package/dist/core/operation-target-groups.d.ts +3 -0
- package/dist/core/operation-target-groups.d.ts.map +1 -0
- package/dist/core/operation-target-groups.js +53 -0
- package/dist/core/operation-target-groups.js.map +1 -0
- package/dist/core/packet-inject.d.ts +0 -1
- package/dist/core/packet-inject.d.ts.map +1 -1
- package/dist/core/packet-inject.js +6 -13
- package/dist/core/packet-inject.js.map +1 -1
- package/dist/core/packet-wire.d.ts +4 -0
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +208 -44
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plurnk-uri.d.ts +1 -1
- package/dist/core/plurnk-uri.d.ts.map +1 -1
- package/dist/core/plurnk-uri.js +3 -3
- package/dist/core/plurnk-uri.js.map +1 -1
- package/dist/core/scheme-types.d.ts +2 -1
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/scheme-types.js.map +1 -1
- package/dist/core/teaching.d.ts.map +1 -1
- package/dist/core/teaching.js +4 -5
- package/dist/core/teaching.js.map +1 -1
- package/dist/core/worker-settings.d.ts +9 -0
- package/dist/core/worker-settings.d.ts.map +1 -0
- package/dist/core/worker-settings.js +25 -0
- package/dist/core/worker-settings.js.map +1 -0
- package/dist/core/workspace-settings.d.ts +0 -11
- package/dist/core/workspace-settings.d.ts.map +1 -1
- package/dist/core/workspace-settings.js +3 -47
- package/dist/core/workspace-settings.js.map +1 -1
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +164 -92
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/digest.sql +8 -2
- package/dist/observe/genai.d.ts +9 -0
- package/dist/observe/genai.d.ts.map +1 -0
- package/dist/observe/genai.js +31 -0
- package/dist/observe/genai.js.map +1 -0
- package/dist/observe/spans.d.ts +3 -3
- package/dist/observe/spans.d.ts.map +1 -1
- package/dist/observe/spans.js +20 -5
- package/dist/observe/spans.js.map +1 -1
- package/dist/schemes/EffectPolicy.d.ts +3 -0
- package/dist/schemes/EffectPolicy.d.ts.map +1 -1
- package/dist/schemes/EffectPolicy.js +38 -6
- package/dist/schemes/EffectPolicy.js.map +1 -1
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +82 -18
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/Log.d.ts +6 -2
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +73 -22
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Log.sql +25 -7
- package/dist/schemes/Prompt.d.ts.map +1 -1
- package/dist/schemes/Prompt.js +1 -2
- package/dist/schemes/Prompt.js.map +1 -1
- package/dist/schemes/QuestionTool.d.ts +10 -0
- package/dist/schemes/QuestionTool.d.ts.map +1 -0
- package/dist/schemes/QuestionTool.js +81 -0
- package/dist/schemes/QuestionTool.js.map +1 -0
- package/dist/schemes/Skill.d.ts.map +1 -1
- package/dist/schemes/Skill.js +4 -1
- package/dist/schemes/Skill.js.map +1 -1
- package/dist/schemes/Worker.d.ts.map +1 -1
- package/dist/schemes/Worker.js +20 -3
- package/dist/schemes/Worker.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +19 -1
- package/dist/schemes/_entry-find.d.ts +5 -3
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +102 -22
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-find.sql +8 -6
- package/dist/schemes/_entry-graph.sql +2 -6
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +12 -1
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_search-index.d.ts.map +1 -1
- package/dist/schemes/_search-index.js +37 -25
- package/dist/schemes/_search-index.js.map +1 -1
- package/dist/server/Daemon.d.ts +69 -12
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +737 -133
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/DaemonModule.d.ts +31 -3
- package/dist/server/DaemonModule.d.ts.map +1 -1
- package/dist/server/DrainSupervisor.d.ts +12 -9
- package/dist/server/DrainSupervisor.d.ts.map +1 -1
- package/dist/server/DrainSupervisor.js +59 -10
- package/dist/server/DrainSupervisor.js.map +1 -1
- package/dist/server/client-input.d.ts +7 -2
- package/dist/server/client-input.d.ts.map +1 -1
- package/dist/server/client-input.js +65 -34
- package/dist/server/client-input.js.map +1 -1
- package/dist/server/dispatch-as-plurnk.d.ts.map +1 -1
- package/dist/server/dispatch-as-plurnk.js +28 -6
- package/dist/server/dispatch-as-plurnk.js.map +1 -1
- package/dist/server/drain.sql +26 -11
- package/dist/server/envelope.d.ts +1 -1
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +4 -4
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/envelope.sql +25 -1
- package/dist/server/lifecycle-recovery.sql +17 -1
- package/dist/server/logEntry.d.ts +2 -1
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +3 -2
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/logEntry.sql +7 -0
- package/dist/server/loop-model.d.ts +2 -2
- package/dist/server/loop-model.d.ts.map +1 -1
- package/dist/server/loop-model.js +23 -33
- package/dist/server/loop-model.js.map +1 -1
- package/dist/server/loopDocs.d.ts +1 -0
- package/dist/server/loopDocs.d.ts.map +1 -1
- package/dist/server/loopDocs.js +94 -14
- package/dist/server/loopDocs.js.map +1 -1
- package/dist/server/loopDocs.sql +12 -0
- package/dist/server/model-catalog.d.ts +3 -0
- package/dist/server/model-catalog.d.ts.map +1 -0
- package/dist/server/model-catalog.js +83 -0
- package/dist/server/model-catalog.js.map +1 -0
- package/dist/server/model-route.d.ts +7 -0
- package/dist/server/model-route.d.ts.map +1 -0
- package/dist/server/model-route.js +47 -0
- package/dist/server/model-route.js.map +1 -0
- package/dist/server/module-discovery.d.ts +13 -0
- package/dist/server/module-discovery.d.ts.map +1 -0
- package/dist/server/module-discovery.js +78 -0
- package/dist/server/module-discovery.js.map +1 -0
- package/dist/server/skillDocs.d.ts +9 -0
- package/dist/server/skillDocs.d.ts.map +1 -0
- package/dist/server/skillDocs.js +212 -0
- package/dist/server/skillDocs.js.map +1 -0
- package/dist/server/skillDocs.sql +14 -0
- package/dist/server/workspace-capabilities.sql +19 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +182 -123
- package/dist/service.js.map +1 -1
- package/migrations/001_schema.sql +455 -65
- package/package.json +38 -37
- package/dist/core/ExecutableTools.d.ts +0 -10
- package/dist/core/ExecutableTools.d.ts.map +0 -1
- package/dist/core/ExecutableTools.js +0 -41
- package/dist/core/ExecutableTools.js.map +0 -1
- package/dist/core/JournalTurn.d.ts +0 -9
- package/dist/core/JournalTurn.d.ts.map +0 -1
- package/dist/core/JournalTurn.js +0 -14
- package/dist/core/JournalTurn.js.map +0 -1
- package/dist/core/JournalTurn.sql +0 -10
- package/dist/core/git-iso.d.ts +0 -13
- package/dist/core/git-iso.d.ts.map +0 -1
- package/dist/core/git-iso.js +0 -267
- package/dist/core/git-iso.js.map +0 -1
- package/dist/core/search-gate.d.ts +0 -17
- package/dist/core/search-gate.d.ts.map +0 -1
- package/dist/core/search-gate.js +0 -77
- package/dist/core/search-gate.js.map +0 -1
- package/dist/digest/Digest.sql +0 -1
package/dist/server/Daemon.js
CHANGED
|
@@ -8,27 +8,34 @@ import ExecutorRegistry from "../core/ExecutorRegistry.js";
|
|
|
8
8
|
import SchemeRegistry from "../core/SchemeRegistry.js";
|
|
9
9
|
import { Mimetypes } from "@plurnk/plurnk-mimetypes";
|
|
10
10
|
import { RuntimeDeclaration } from "@plurnk/plurnk-execs";
|
|
11
|
+
import { projectModelRoute, routeForSpec, specForRoute } from "./model-route.js";
|
|
12
|
+
import { discoverDaemonModules } from "./module-discovery.js";
|
|
13
|
+
import WorkerSettingsReader from "../core/worker-settings.js";
|
|
14
|
+
import EffectPolicy from "../schemes/EffectPolicy.js";
|
|
15
|
+
import QuestionTool, { questionRuntimeDecl } from "../schemes/QuestionTool.js";
|
|
11
16
|
import DrainSupervisor from "./DrainSupervisor.js";
|
|
12
17
|
import { parsePath, Validator, } from "@plurnk/plurnk-contracts";
|
|
13
18
|
import LogEntry from "./logEntry.js";
|
|
14
19
|
import Envelope from "./envelope.js";
|
|
15
20
|
import ClientInput from "./client-input.js";
|
|
16
|
-
import
|
|
21
|
+
import Turn from "../core/Turn.js";
|
|
17
22
|
import LoopDocs from "./loopDocs.js";
|
|
23
|
+
import SkillDocs from "./skillDocs.js";
|
|
18
24
|
import GitMembership from "../core/git-membership.js";
|
|
19
25
|
import Fork from "../core/fork.js";
|
|
20
26
|
import WorkerName from "../core/WorkerName.js";
|
|
21
27
|
import LoopLifecycle from "../core/LoopLifecycle.js";
|
|
22
28
|
import { promptLoopPrefix } from "../core/plurnk-uri.js";
|
|
23
29
|
import { contentWeight } from "../core/content-weight.js";
|
|
24
|
-
import { parseAliasesFromEnv,
|
|
30
|
+
import { parseAliasesFromEnv, resolveActiveRoute, UnsupportedReasoningPolicyError, } from "@plurnk/plurnk-providers";
|
|
25
31
|
import ProviderInstantiate from "../core/ProviderInstantiate.js";
|
|
26
|
-
import {
|
|
32
|
+
import { resolveLoopRoute } from "./loop-model.js";
|
|
27
33
|
import LoopFlagsReader from "../core/LoopFlagsReader.js";
|
|
28
34
|
import Results, { OperationFailureError } from "../core/results.js";
|
|
29
35
|
import WorkspaceGate from "../core/WorkspaceGate.js";
|
|
30
36
|
import BranchBatches from "./BranchBatches.js";
|
|
31
37
|
import { observed, observedSync } from "../observe/spans.js";
|
|
38
|
+
import { listModelCatalog } from "./model-catalog.js";
|
|
32
39
|
const clientActionFailure = (error) => {
|
|
33
40
|
if (error instanceof OperationFailureError)
|
|
34
41
|
return error.result;
|
|
@@ -40,6 +47,9 @@ const clientActionFailure = (error) => {
|
|
|
40
47
|
};
|
|
41
48
|
const daemonFailure = (owner, code, status, detail, extensions = {}) => new OperationFailureError(Results.failure(owner, code, status, detail, {}, extensions));
|
|
42
49
|
const entryReadResult = (result) => Validator.assertEntryReadResult(result);
|
|
50
|
+
const modelRouteLabel = (route) => route.alias === undefined
|
|
51
|
+
? `model route '${route.provider}/${route.model}'`
|
|
52
|
+
: `provider alias '${route.alias}' (${route.provider}/${route.model})`;
|
|
43
53
|
export default class Daemon {
|
|
44
54
|
#db;
|
|
45
55
|
#engine;
|
|
@@ -58,6 +68,7 @@ export default class Daemon {
|
|
|
58
68
|
#modules = [];
|
|
59
69
|
#moduleClosers = [];
|
|
60
70
|
#moduleActions = new Map();
|
|
71
|
+
#workspaceCapabilityProviders = new Map();
|
|
61
72
|
// {§methods-event-subscribe} — the broadcast's in-process event source. A transport
|
|
62
73
|
// module (plurnk-agui) subscribes and fans out to its OWN clients; core emits, never owns
|
|
63
74
|
// client transport or connection state.
|
|
@@ -82,7 +93,7 @@ export default class Daemon {
|
|
|
82
93
|
defaultMimetype: "text/markdown",
|
|
83
94
|
discoverOptions: { cwd: this.#discoveryCwd },
|
|
84
95
|
});
|
|
85
|
-
const bootSpec =
|
|
96
|
+
const bootSpec = resolveActiveRoute();
|
|
86
97
|
if (this.#provider !== null && bootSpec !== null) {
|
|
87
98
|
ProviderInstantiate.registerInstance(this.#provider, bootSpec);
|
|
88
99
|
}
|
|
@@ -109,10 +120,19 @@ export default class Daemon {
|
|
|
109
120
|
}))?.id;
|
|
110
121
|
if (workerId === undefined)
|
|
111
122
|
throw new Error("Branch worker insert returned no row");
|
|
123
|
+
// {§worker-model-selection} — lineage inheritance by value: the branch
|
|
124
|
+
// child begins with the spawning loop's effective spawn model.
|
|
125
|
+
await this.#db.worker_generation_policy_update.run({
|
|
126
|
+
id: workerId,
|
|
127
|
+
model_route_id: await routeForSpec(this.#db, providerSpec),
|
|
128
|
+
spawn_model_route_id: null,
|
|
129
|
+
reasoning_policy: parentPolicy.reasoningPolicy,
|
|
130
|
+
});
|
|
112
131
|
const loopId = await this.#drains.enqueueFreshLoop({
|
|
113
132
|
workerId,
|
|
114
133
|
prompt,
|
|
115
134
|
providerSpec,
|
|
135
|
+
reasoningPolicy: parentPolicy.reasoningPolicy,
|
|
116
136
|
childProviderSpec: parentPolicy.childProviderSpec,
|
|
117
137
|
flags,
|
|
118
138
|
});
|
|
@@ -152,20 +172,44 @@ export default class Daemon {
|
|
|
152
172
|
// independently (the sister is its own worker). {§machine-processes}
|
|
153
173
|
injectWorker: async ({ workspaceId, workerId, prompt, flags, parentLoopId }) => {
|
|
154
174
|
const systemPrompt = await readFile(Paths.instructionsSystem, "utf8");
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
175
|
+
let providerSpec;
|
|
176
|
+
let childProviderSpec;
|
|
177
|
+
let reasoningPolicy;
|
|
178
|
+
if (parentLoopId === undefined) {
|
|
179
|
+
// {§worker-model-selection} — the voice door addresses an
|
|
180
|
+
// existing worker. Its own durable generation policy is the
|
|
181
|
+
// receiver's identity; the daemon default and sender do not
|
|
182
|
+
// overwrite it merely because a new loop must be minted.
|
|
183
|
+
const targetPolicy = await this.#resolveWorkerModel(workerId, undefined);
|
|
184
|
+
if (targetPolicy === null) {
|
|
185
|
+
throw new Error(`injectWorker: worker ${workerId} has no resolvable model route`);
|
|
186
|
+
}
|
|
187
|
+
providerSpec = targetPolicy.providerSpec;
|
|
188
|
+
reasoningPolicy = targetPolicy.reasoningPolicy;
|
|
189
|
+
childProviderSpec = await this.#resolveWorkerSpawnModel(workerId, undefined);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
const parentPolicy = await this.#providerPolicyForLoop(parentLoopId);
|
|
193
|
+
providerSpec = parentPolicy.childProviderSpec ?? parentPolicy.providerSpec;
|
|
194
|
+
reasoningPolicy = parentPolicy.reasoningPolicy;
|
|
195
|
+
// {§worker-model-selection} — lineage inheritance by value:
|
|
196
|
+
// the child begins with the spawning loop's effective spawn
|
|
197
|
+
// model and no separate override of its own.
|
|
198
|
+
childProviderSpec = null;
|
|
199
|
+
await this.#db.worker_generation_policy_update.run({
|
|
200
|
+
id: workerId,
|
|
201
|
+
model_route_id: await routeForSpec(this.#db, providerSpec),
|
|
202
|
+
spawn_model_route_id: null,
|
|
203
|
+
reasoning_policy: reasoningPolicy,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
163
206
|
const { action, loopId } = await this.inject({
|
|
164
207
|
workspaceId,
|
|
165
208
|
workerId,
|
|
166
209
|
prompt,
|
|
167
210
|
providerSpec,
|
|
168
|
-
|
|
211
|
+
reasoningPolicy,
|
|
212
|
+
childProviderSpec,
|
|
169
213
|
systemPrompt,
|
|
170
214
|
...(flags === undefined ? {} : { flags }),
|
|
171
215
|
});
|
|
@@ -174,7 +218,15 @@ export default class Daemon {
|
|
|
174
218
|
branchWorker: async (args) => this.#branchBatches.enqueue(args),
|
|
175
219
|
branchCompletionGate: async (workerId) => this.#branchBatches.completionGate(workerId),
|
|
176
220
|
acquireWorkspaceTurn: async (workspaceId, workerId) => this.#workspaceGate.acquireTurn(workspaceId, workerId),
|
|
177
|
-
|
|
221
|
+
// {§skills-materialization} — filesystem installers operate out of
|
|
222
|
+
// band. Refresh under the workspace turn gate before packet assembly
|
|
223
|
+
// so the first subsequent model turn sees their exact result.
|
|
224
|
+
workspaceTurnStarting: async ({ workspaceId }) => {
|
|
225
|
+
await SkillDocs.refreshIfChanged(this.#engine, this.#db, workspaceId);
|
|
226
|
+
},
|
|
227
|
+
workspaceTurnCompleted: async ({ turnId }) => {
|
|
228
|
+
await this.#branchBatches.sealTurn(turnId);
|
|
229
|
+
},
|
|
178
230
|
// worker:// KILL (terminate) — cancel the addressed worker subtree and
|
|
179
231
|
// tear down its held streams before the operation completes.
|
|
180
232
|
cancelWorker: async (workerId, reason) => this.#drains.cancelWorkerTree(workerId, reason),
|
|
@@ -185,9 +237,14 @@ export default class Daemon {
|
|
|
185
237
|
db,
|
|
186
238
|
lifecycle: this.#lifecycle,
|
|
187
239
|
injectPrompt: (workerId, prompt, openPaths) => this.#engine.inject(workerId, prompt, openPaths),
|
|
188
|
-
assertInjectionCompatibility: async ({ workerId, loopId, providerSpec, childProviderSpec, turnCeiling, flags, }) => {
|
|
240
|
+
assertInjectionCompatibility: async ({ workerId, loopId, providerSpec, providerSpecExplicit, reasoningPolicy, childProviderSpec, turnCeiling, flags, }) => {
|
|
189
241
|
await this.#assertFoldPosture(workerId, flags, loopId);
|
|
190
|
-
|
|
242
|
+
// An omitted selector keeps the loop's durable provider; only an
|
|
243
|
+
// explicit selection is checked against it (a deliberate switch).
|
|
244
|
+
if (providerSpecExplicit !== false) {
|
|
245
|
+
await this.#assertLoopProvider(loopId, providerSpec);
|
|
246
|
+
}
|
|
247
|
+
await this.#assertLoopReasoningPolicy(loopId, reasoningPolicy);
|
|
191
248
|
if (childProviderSpec !== undefined) {
|
|
192
249
|
await this.#assertLoopChildProvider(loopId, childProviderSpec);
|
|
193
250
|
}
|
|
@@ -207,7 +264,6 @@ export default class Daemon {
|
|
|
207
264
|
{ role: "system", content: systemPrompt },
|
|
208
265
|
{ role: "user", content: prompt },
|
|
209
266
|
],
|
|
210
|
-
origin: "model",
|
|
211
267
|
signal,
|
|
212
268
|
onDispatch,
|
|
213
269
|
});
|
|
@@ -219,7 +275,6 @@ export default class Daemon {
|
|
|
219
275
|
this.#engine.parkDeadlines.delete(loopId);
|
|
220
276
|
return deadline;
|
|
221
277
|
},
|
|
222
|
-
settleSearch: (target, status) => this.#engine.searchGate.settle(target, status),
|
|
223
278
|
cancelSubscription: (subscriptionId) => this.#engine.cancelSubscription(subscriptionId),
|
|
224
279
|
hasActiveStreams: (workerId) => this.#workerHasActiveStreams(workerId),
|
|
225
280
|
readSystemPrompt: () => readFile(Paths.instructionsSystem, "utf8"),
|
|
@@ -235,6 +290,10 @@ export default class Daemon {
|
|
|
235
290
|
const { workspaceId, ...proposal } = event;
|
|
236
291
|
this.#broadcast({ workspaceId }, "loop/proposal", proposal);
|
|
237
292
|
});
|
|
293
|
+
this.#engine.onClientInteractionPending((event) => {
|
|
294
|
+
const { workspaceId, ...interaction } = event;
|
|
295
|
+
this.#broadcast({ workspaceId }, "loop/interaction", interaction);
|
|
296
|
+
});
|
|
238
297
|
}
|
|
239
298
|
// {§methods-event-subscribe}. A transport module subscribes to the daemon's in-process
|
|
240
299
|
// event source: it receives every workspace-scoped engine event as `(workspaceId, method, params)`
|
|
@@ -258,6 +317,15 @@ export default class Daemon {
|
|
|
258
317
|
const checkedResolution = ClientInput.assertProposalResolution("resolveProposal", resolution);
|
|
259
318
|
this.#engine.resolveProposal(checkedLogEntryId, checkedResolution);
|
|
260
319
|
}
|
|
320
|
+
async pendingClientInteractions(workspaceId) {
|
|
321
|
+
const checkedWorkspaceId = ClientInput.assertId("pendingClientInteractions", "workspaceId", workspaceId);
|
|
322
|
+
return this.#engine.pendingClientInteractions(checkedWorkspaceId);
|
|
323
|
+
}
|
|
324
|
+
async resolveClientInteraction(interactionId, resolution) {
|
|
325
|
+
const checkedInteractionId = ClientInput.assertId("resolveClientInteraction", "interactionId", interactionId);
|
|
326
|
+
const checkedResolution = ClientInput.assertClientInteractionResolution("resolveClientInteraction", resolution);
|
|
327
|
+
await this.#engine.resolveClientInteraction(checkedInteractionId, checkedResolution);
|
|
328
|
+
}
|
|
261
329
|
// {§methods-loop-run} — drive/steer a loop. The module supplies only workspace/worker/prompt;
|
|
262
330
|
// the provider and the law-file system prompt are core's and stay inside. Returns immediately — the
|
|
263
331
|
// loop runs async and its outcome arrives on the event source (loop/terminated). `cancelDrain` (public)
|
|
@@ -268,34 +336,25 @@ export default class Daemon {
|
|
|
268
336
|
const prompt = ClientInput.assertPrompt("runLoop", args.prompt);
|
|
269
337
|
const requestedMaxTurns = ClientInput.assertMaxTurns("runLoop", args.maxTurns);
|
|
270
338
|
const openPaths = ClientInput.assertOpenPaths("runLoop", args.openPaths);
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
const childAlias = ClientInput.assertOptionalChildAlias("runLoop", args.childAlias);
|
|
274
|
-
const childModel = ClientInput.assertOptionalSelector("runLoop", "childModel", args.childModel);
|
|
275
|
-
if (childAlias === null && childModel !== undefined) {
|
|
276
|
-
throw daemonFailure("daemon:input", "child-provider-conflict", 400, "childModel cannot accompany an inherited child provider policy.", { field: "childModel", recovery: "Omit childModel or select a child alias.", retryable: false });
|
|
277
|
-
}
|
|
339
|
+
const selector = ClientInput.assertOptionalSelector("runLoop", "selector", args.selector);
|
|
340
|
+
const childSelector = ClientInput.assertOptionalChildSelector("runLoop", args.childSelector);
|
|
278
341
|
const flags = ClientInput.normalizeLoopFlags("runLoop", args.flags);
|
|
279
|
-
// {§
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
342
|
+
// {§worker-model-selection} — the worker owns the model. An explicit selector
|
|
343
|
+
// persists onto the worker; an omitted selector resolves the worker's durable model
|
|
344
|
+
// (seeded once from the daemon default). The loop then snapshots the resolved route.
|
|
345
|
+
// A continuation's omitted selector must still keep the loop's durable provider.
|
|
346
|
+
const selectorExplicit = selector !== undefined;
|
|
347
|
+
const generationPolicy = await this.#resolveWorkerModel(workerId, selector);
|
|
348
|
+
if (generationPolicy === null) {
|
|
349
|
+
throw new OperationFailureError(Results.failure("daemon:provider", "not-configured", 501, "No provider is configured for this worker.", {}, {
|
|
286
350
|
stage: "provider-selection",
|
|
287
351
|
recovery: "Select a configured model provider.",
|
|
288
352
|
retryable: false,
|
|
289
353
|
}));
|
|
290
354
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
: childAlias;
|
|
295
|
-
const childSelection = configuredChildAlias === null
|
|
296
|
-
|| (configuredChildAlias === undefined && childModel === undefined)
|
|
297
|
-
? null
|
|
298
|
-
: await this.#resolveLoopProvider(configuredChildAlias, childModel);
|
|
355
|
+
const { providerSpec: selection, reasoningPolicy } = generationPolicy;
|
|
356
|
+
// {§methods-loop-run-child-provider} — the worker's persistent spawn override.
|
|
357
|
+
const childSelection = await this.#resolveWorkerSpawnModel(workerId, childSelector);
|
|
299
358
|
const systemPrompt = await readFile(Paths.instructionsSystem, "utf8");
|
|
300
359
|
// {§machine-processes} — the model NEVER runs in a client-origin worker (its packets would carry
|
|
301
360
|
// client-action rows). The module resolves the model worker via ensureModelWorker and passes it (or a
|
|
@@ -336,72 +395,182 @@ export default class Daemon {
|
|
|
336
395
|
...(openPaths !== undefined ? { openPaths } : {}),
|
|
337
396
|
turnCeiling,
|
|
338
397
|
providerSpec: selection,
|
|
398
|
+
providerSpecExplicit: selectorExplicit,
|
|
399
|
+
reasoningPolicy,
|
|
339
400
|
childProviderSpec: childSelection,
|
|
340
401
|
systemPrompt,
|
|
341
402
|
});
|
|
342
403
|
return { status: 100, action, loopId, ...(turnSeq !== undefined ? { turnSeq } : {}) };
|
|
343
404
|
}
|
|
344
|
-
// {§methods-loop-run-model} — resolve
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const requested = resolveLoopAlias(alias, model, parseAliasesFromEnv());
|
|
405
|
+
// {§methods-loop-run-model} — resolve one alias-or-route selector to a cached
|
|
406
|
+
// Provider; absent uses the boot default. An unknown alias or malformed exact
|
|
407
|
+
// route throws legibly rather than silently running the wrong model.
|
|
408
|
+
async #resolveLoopProvider(selector, reasoningPolicy) {
|
|
409
|
+
const requested = resolveLoopRoute(selector, parseAliasesFromEnv());
|
|
350
410
|
if (requested === null && this.#provider === null)
|
|
351
411
|
return null;
|
|
352
|
-
const spec = requested ??
|
|
412
|
+
const spec = requested ?? resolveActiveRoute();
|
|
353
413
|
if (spec === null) {
|
|
354
|
-
throw daemonFailure("daemon:provider", "active-
|
|
414
|
+
throw daemonFailure("daemon:provider", "active-model-unresolved", 500, "The active provider has no resolvable model route.", { stage: "provider-selection", retryable: false });
|
|
355
415
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
416
|
+
await this.#providerForPolicy(spec, reasoningPolicy);
|
|
417
|
+
return spec;
|
|
418
|
+
}
|
|
419
|
+
// Resolve eagerly so runLoop fails before enqueue when the selected route
|
|
420
|
+
// and durable reasoning policy cannot compose. The drain later retrieves
|
|
421
|
+
// this cached handle from the loop's immutable snapshot.
|
|
422
|
+
async #providerForPolicy(spec, reasoningPolicy) {
|
|
359
423
|
try {
|
|
360
|
-
await ProviderInstantiate.instantiateProvider(spec);
|
|
424
|
+
const provider = await ProviderInstantiate.instantiateProvider(spec, process.env, reasoningPolicy);
|
|
425
|
+
ProviderInstantiate.validateGrammarConfiguration(provider, process.env, reasoningPolicy);
|
|
426
|
+
return provider;
|
|
361
427
|
}
|
|
362
428
|
catch (cause) {
|
|
363
429
|
if (cause instanceof OperationFailureError)
|
|
364
430
|
throw cause;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
431
|
+
if (cause instanceof UnsupportedReasoningPolicyError) {
|
|
432
|
+
throw daemonFailure("daemon:provider", "reasoning-policy-unsupported", 409, `${modelRouteLabel(spec)} does not support reasoning policy '${cause.policy}'.`, {
|
|
433
|
+
...(spec.alias === undefined ? {} : { alias: spec.alias }),
|
|
434
|
+
provider: spec.provider,
|
|
435
|
+
model: spec.model,
|
|
436
|
+
reasoningPolicy: cause.policy,
|
|
437
|
+
supportedReasoningPolicies: cause.supported,
|
|
438
|
+
stage: "provider-selection",
|
|
439
|
+
recovery: "Select one of the provider's supported reasoning policies.",
|
|
440
|
+
retryable: false,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
console.error(`${modelRouteLabel(spec)} could not be instantiated:`, cause);
|
|
444
|
+
throw daemonFailure("daemon:provider", "provider-unavailable", 503, `${modelRouteLabel(spec)} is unavailable.`, {
|
|
445
|
+
...(spec.alias === undefined ? {} : { alias: spec.alias }),
|
|
368
446
|
provider: spec.provider,
|
|
369
447
|
model: spec.model,
|
|
370
448
|
stage: "provider-selection",
|
|
371
449
|
retryable: false,
|
|
372
450
|
});
|
|
373
451
|
}
|
|
374
|
-
return spec;
|
|
375
452
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
453
|
+
// {§worker-model-selection} — a model worker owns one durable model. An explicit
|
|
454
|
+
// selector persists onto the worker; an omitted selector resolves the worker's
|
|
455
|
+
// durable model, seeded once from the daemon default. A deliberately modelless
|
|
456
|
+
// daemon leaves the worker unset until an explicit selection arrives.
|
|
457
|
+
async #resolveWorkerModel(workerId, selector) {
|
|
458
|
+
const worker = await this.#db.worker_generation_policy_read.get({ id: workerId });
|
|
459
|
+
if (worker === undefined)
|
|
460
|
+
throw new Error(`worker ${workerId}: model route row missing`);
|
|
461
|
+
if (selector !== undefined) {
|
|
462
|
+
const spec = await this.#resolveLoopProvider(selector, worker.reasoning_policy ?? undefined);
|
|
463
|
+
if (spec === null)
|
|
464
|
+
return null;
|
|
465
|
+
const reasoningPolicy = worker.reasoning_policy
|
|
466
|
+
?? ProviderInstantiate.configuredReasoningPolicy(spec);
|
|
467
|
+
if (worker.spawn_model_route_id !== null) {
|
|
468
|
+
const spawnSpec = await specForRoute(this.#db, worker.spawn_model_route_id);
|
|
469
|
+
if (spawnSpec === null)
|
|
470
|
+
throw new Error(`worker ${workerId}: spawn model route is missing`);
|
|
471
|
+
await this.#providerForPolicy(spawnSpec, reasoningPolicy);
|
|
472
|
+
}
|
|
473
|
+
await this.#db.worker_generation_policy_update.run({
|
|
474
|
+
id: workerId,
|
|
475
|
+
model_route_id: await routeForSpec(this.#db, spec),
|
|
476
|
+
spawn_model_route_id: worker.spawn_model_route_id,
|
|
477
|
+
reasoning_policy: reasoningPolicy,
|
|
478
|
+
});
|
|
479
|
+
return { providerSpec: spec, reasoningPolicy };
|
|
380
480
|
}
|
|
381
|
-
|
|
382
|
-
|
|
481
|
+
if (worker.model_route_id !== null) {
|
|
482
|
+
if (worker.reasoning_policy === null) {
|
|
483
|
+
throw new Error(`worker ${workerId}: durable model has no reasoning policy`);
|
|
484
|
+
}
|
|
485
|
+
const spec = await specForRoute(this.#db, worker.model_route_id);
|
|
486
|
+
if (spec === null)
|
|
487
|
+
throw new Error(`worker ${workerId}: model route is missing`);
|
|
488
|
+
await this.#providerForPolicy(spec, worker.reasoning_policy);
|
|
489
|
+
return { providerSpec: spec, reasoningPolicy: worker.reasoning_policy };
|
|
383
490
|
}
|
|
384
|
-
if (
|
|
491
|
+
if (this.#provider === null)
|
|
385
492
|
return null;
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
493
|
+
const spec = resolveActiveRoute();
|
|
494
|
+
if (spec !== null) {
|
|
495
|
+
const reasoningPolicy = ProviderInstantiate.configuredReasoningPolicy(spec);
|
|
496
|
+
await this.#providerForPolicy(spec, reasoningPolicy);
|
|
497
|
+
if (worker.spawn_model_route_id !== null) {
|
|
498
|
+
const spawnSpec = await specForRoute(this.#db, worker.spawn_model_route_id);
|
|
499
|
+
if (spawnSpec === null)
|
|
500
|
+
throw new Error(`worker ${workerId}: spawn model route is missing`);
|
|
501
|
+
await this.#providerForPolicy(spawnSpec, reasoningPolicy);
|
|
502
|
+
}
|
|
503
|
+
await this.#db.worker_generation_policy_update.run({
|
|
504
|
+
id: workerId,
|
|
505
|
+
model_route_id: await routeForSpec(this.#db, spec),
|
|
506
|
+
spawn_model_route_id: worker.spawn_model_route_id,
|
|
507
|
+
reasoning_policy: reasoningPolicy,
|
|
508
|
+
});
|
|
509
|
+
return { providerSpec: spec, reasoningPolicy };
|
|
391
510
|
}
|
|
392
|
-
return
|
|
511
|
+
return null;
|
|
393
512
|
}
|
|
513
|
+
// {§worker-model-selection} — the persistent spawn override. An explicit child
|
|
514
|
+
// selector persists onto the worker (null clears it back to inherit); an omitted
|
|
515
|
+
// selector resolves the persisted override, seeded once from the operator's
|
|
516
|
+
// PLURNK_MODEL_CHILD default.
|
|
517
|
+
async #resolveWorkerSpawnModel(workerId, childSelector) {
|
|
518
|
+
const worker = await this.#db.worker_generation_policy_read.get({ id: workerId });
|
|
519
|
+
if (worker === undefined)
|
|
520
|
+
throw new Error(`worker ${workerId}: model route row missing`);
|
|
521
|
+
if (childSelector !== undefined) {
|
|
522
|
+
const spec = childSelector === null
|
|
523
|
+
? null
|
|
524
|
+
: await this.#resolveLoopProvider(childSelector, worker.reasoning_policy ?? undefined);
|
|
525
|
+
await this.#db.worker_generation_policy_update.run({
|
|
526
|
+
id: workerId,
|
|
527
|
+
model_route_id: worker.model_route_id,
|
|
528
|
+
spawn_model_route_id: spec === null ? null : await routeForSpec(this.#db, spec),
|
|
529
|
+
reasoning_policy: worker.reasoning_policy,
|
|
530
|
+
});
|
|
531
|
+
return spec;
|
|
532
|
+
}
|
|
533
|
+
if (worker.spawn_model_route_id !== null) {
|
|
534
|
+
const spec = await specForRoute(this.#db, worker.spawn_model_route_id);
|
|
535
|
+
if (spec === null)
|
|
536
|
+
throw new Error(`worker ${workerId}: spawn model route is missing`);
|
|
537
|
+
if (worker.reasoning_policy !== null) {
|
|
538
|
+
await this.#providerForPolicy(spec, worker.reasoning_policy);
|
|
539
|
+
}
|
|
540
|
+
return spec;
|
|
541
|
+
}
|
|
542
|
+
const configured = process.env.PLURNK_MODEL_CHILD;
|
|
543
|
+
if (configured === undefined || configured.length === 0)
|
|
544
|
+
return null;
|
|
545
|
+
const spec = await this.#resolveLoopProvider(configured, worker.reasoning_policy ?? undefined);
|
|
546
|
+
if (spec !== null) {
|
|
547
|
+
await this.#db.worker_generation_policy_update.run({
|
|
548
|
+
id: workerId,
|
|
549
|
+
model_route_id: worker.model_route_id,
|
|
550
|
+
spawn_model_route_id: await routeForSpec(this.#db, spec),
|
|
551
|
+
reasoning_policy: worker.reasoning_policy,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
return spec;
|
|
555
|
+
}
|
|
556
|
+
// {§worker-model-selection} — the loop's durable snapshot is the immutable route ids,
|
|
557
|
+
// resolved through model_routes at the claim boundary; never re-resolved through the
|
|
558
|
+
// alias cascade (a changed alias declaration must not rewrite history).
|
|
394
559
|
async #providerPolicyForLoop(loopId) {
|
|
395
|
-
const row = await this.#db.
|
|
560
|
+
const row = await this.#db.drain_loop_generation_policy.get({ loop_id: loopId });
|
|
396
561
|
if (row === undefined)
|
|
397
562
|
throw new Error(`loop ${loopId}: provider selection row is missing`);
|
|
398
|
-
const providerSpec = this.#
|
|
563
|
+
const providerSpec = await specForRoute(this.#db, row.model_route_id);
|
|
399
564
|
if (providerSpec === null) {
|
|
400
|
-
throw new Error(`loop ${loopId}: persisted provider selection is missing
|
|
565
|
+
throw new Error(`loop ${loopId}: persisted provider selection is missing — refusing boot-default substitution`);
|
|
566
|
+
}
|
|
567
|
+
if (row.reasoning_policy === null) {
|
|
568
|
+
throw new Error(`loop ${loopId}: persisted reasoning policy is missing`);
|
|
401
569
|
}
|
|
402
570
|
return {
|
|
403
571
|
providerSpec,
|
|
404
|
-
childProviderSpec: this.#
|
|
572
|
+
childProviderSpec: await specForRoute(this.#db, row.spawn_model_route_id),
|
|
573
|
+
reasoningPolicy: row.reasoning_policy,
|
|
405
574
|
};
|
|
406
575
|
}
|
|
407
576
|
async #providerSpecForLoop(loopId) {
|
|
@@ -409,20 +578,20 @@ export default class Daemon {
|
|
|
409
578
|
}
|
|
410
579
|
async #providersForLoop(loopId) {
|
|
411
580
|
const policy = await this.#providerPolicyForLoop(loopId);
|
|
412
|
-
const provider = await
|
|
581
|
+
const provider = await this.#providerForPolicy(policy.providerSpec, policy.reasoningPolicy);
|
|
413
582
|
const childProvider = policy.childProviderSpec === null
|
|
414
583
|
? provider
|
|
415
|
-
: await
|
|
584
|
+
: await this.#providerForPolicy(policy.childProviderSpec, policy.reasoningPolicy);
|
|
416
585
|
return { provider, childProvider };
|
|
417
586
|
}
|
|
418
587
|
async #assertLoopProvider(loopId, requested) {
|
|
419
588
|
const selected = await this.#providerSpecForLoop(loopId);
|
|
420
589
|
if (JSON.stringify(selected) !== JSON.stringify(requested)) {
|
|
421
|
-
throw daemonFailure("daemon:provider", "loop-provider-conflict", 409, `Loop ${loopId} uses
|
|
590
|
+
throw daemonFailure("daemon:provider", "loop-provider-conflict", 409, `Loop ${loopId} uses ${modelRouteLabel(selected)}, not ${modelRouteLabel(requested)}.`, {
|
|
422
591
|
loopId,
|
|
423
|
-
selectedAlias: selected.alias,
|
|
592
|
+
...(selected.alias === undefined ? {} : { selectedAlias: selected.alias }),
|
|
424
593
|
selectedModel: `${selected.provider}/${selected.model}`,
|
|
425
|
-
requestedAlias: requested.alias,
|
|
594
|
+
...(requested.alias === undefined ? {} : { requestedAlias: requested.alias }),
|
|
426
595
|
requestedModel: `${requested.provider}/${requested.model}`,
|
|
427
596
|
stage: "loop-injection",
|
|
428
597
|
recovery: "Cancel or conclude the loop before selecting another provider.",
|
|
@@ -430,13 +599,28 @@ export default class Daemon {
|
|
|
430
599
|
});
|
|
431
600
|
}
|
|
432
601
|
}
|
|
602
|
+
async #assertLoopReasoningPolicy(loopId, requested) {
|
|
603
|
+
const selected = (await this.#providerPolicyForLoop(loopId)).reasoningPolicy;
|
|
604
|
+
if (selected !== requested) {
|
|
605
|
+
throw daemonFailure("daemon:provider", "loop-reasoning-policy-conflict", 409, `Loop ${loopId} uses reasoning policy '${selected}', not '${requested}'.`, {
|
|
606
|
+
loopId,
|
|
607
|
+
selectedReasoningPolicy: selected,
|
|
608
|
+
requestedReasoningPolicy: requested,
|
|
609
|
+
stage: "loop-injection",
|
|
610
|
+
recovery: "Cancel or conclude the loop before selecting another reasoning policy.",
|
|
611
|
+
retryable: false,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
433
615
|
async #assertLoopChildProvider(loopId, requested) {
|
|
434
616
|
const selected = (await this.#providerPolicyForLoop(loopId)).childProviderSpec;
|
|
435
617
|
if (JSON.stringify(selected) !== JSON.stringify(requested)) {
|
|
436
618
|
throw daemonFailure("daemon:provider", "loop-child-provider-conflict", 409, `Loop ${loopId} already has a different child provider policy.`, {
|
|
437
619
|
loopId,
|
|
438
620
|
selectedChildAlias: selected?.alias ?? null,
|
|
621
|
+
selectedChildModel: selected === null ? null : `${selected.provider}/${selected.model}`,
|
|
439
622
|
requestedChildAlias: requested?.alias ?? null,
|
|
623
|
+
requestedChildModel: requested === null ? null : `${requested.provider}/${requested.model}`,
|
|
440
624
|
stage: "loop-injection",
|
|
441
625
|
recovery: "Cancel or conclude the loop before changing its child provider policy.",
|
|
442
626
|
retryable: false,
|
|
@@ -462,10 +646,180 @@ export default class Daemon {
|
|
|
462
646
|
}
|
|
463
647
|
// {§methods-model-worker} — the workspace's model worker (created on first use), distinct from the client
|
|
464
648
|
// worker so the model's packets never carry client-action rows. The module binds its threads to this.
|
|
465
|
-
|
|
466
|
-
|
|
649
|
+
// Optional worker settings ({§worker-settings}) ride the client's per-run declaration: merged in on
|
|
650
|
+
// creation AND on every subsequent ensure, so a client can change its mind between loops.
|
|
651
|
+
async ensureModelWorker(workspaceId, settings) {
|
|
652
|
+
const checked = ClientInput.assertId("worker.ensure-model", "workspaceId", workspaceId);
|
|
653
|
+
const created = await Envelope.ensureModelWorker(this.#db, checked);
|
|
654
|
+
if (settings !== undefined)
|
|
655
|
+
await this.#mergeWorkerSettings(created, settings);
|
|
656
|
+
return created;
|
|
657
|
+
}
|
|
658
|
+
// {§worker-settings} — merge known keys into the worker's behavioral-rules bag.
|
|
659
|
+
// Validated at the boundary; unprovided keys keep their durable value.
|
|
660
|
+
async #mergeWorkerSettings(workerId, settings) {
|
|
661
|
+
const current = await WorkerSettingsReader.read(this.#db, workerId);
|
|
662
|
+
const merged = {
|
|
663
|
+
requestUserInput: settings.requestUserInput ?? current.requestUserInput,
|
|
664
|
+
};
|
|
665
|
+
await this.#db.worker_settings_update.run({ id: workerId, settings: JSON.stringify(merged) });
|
|
666
|
+
}
|
|
667
|
+
// {§worker-settings} — project the worker's behavioral rules for a client-interface surface.
|
|
668
|
+
async readWorkerSettings(args) {
|
|
669
|
+
const workspaceId = ClientInput.assertId("worker.settings.get", "workspaceId", args.workspaceId);
|
|
670
|
+
const workerId = ClientInput.assertId("worker.settings.get", "workerId", args.workerId);
|
|
671
|
+
await this.#assertWorkerOwned(workspaceId, workerId);
|
|
672
|
+
const settings = await WorkerSettingsReader.read(this.#db, workerId);
|
|
673
|
+
return { requestUserInput: settings.requestUserInput };
|
|
674
|
+
}
|
|
675
|
+
// {§worker-settings} — persist known behavioral-rule keys; unknown keys are
|
|
676
|
+
// rejected at the input boundary, never persisted.
|
|
677
|
+
async setWorkerSettings(args) {
|
|
678
|
+
const workspaceId = ClientInput.assertId("worker.settings.set", "workspaceId", args.workspaceId);
|
|
679
|
+
const workerId = ClientInput.assertId("worker.settings.set", "workerId", args.workerId);
|
|
680
|
+
await this.#assertWorkerOwned(workspaceId, workerId);
|
|
681
|
+
ClientInput.parseWorkerSettings(args.settings);
|
|
682
|
+
await this.#mergeWorkerSettings(workerId, args.settings);
|
|
683
|
+
const settings = await WorkerSettingsReader.read(this.#db, workerId);
|
|
684
|
+
return { requestUserInput: settings.requestUserInput };
|
|
685
|
+
}
|
|
686
|
+
// {§worker-model-selection} — project a worker's durable model and spawn override
|
|
687
|
+
// for a client-interface get/set surface. Ownership is checked at this boundary.
|
|
688
|
+
async readWorkerModel(args) {
|
|
689
|
+
const workspaceId = ClientInput.assertId("worker.model.get", "workspaceId", args.workspaceId);
|
|
690
|
+
const workerId = ClientInput.assertId("worker.model.get", "workerId", args.workerId);
|
|
691
|
+
await this.#assertWorkerOwned(workspaceId, workerId);
|
|
692
|
+
const row = await this.#db.worker_generation_policy_read.get({ id: workerId });
|
|
693
|
+
if (row === undefined)
|
|
694
|
+
throw new Error(`worker ${workerId}: model route row missing`);
|
|
695
|
+
const modelSpec = await specForRoute(this.#db, row.model_route_id);
|
|
696
|
+
const spawnModelSpec = await specForRoute(this.#db, row.spawn_model_route_id);
|
|
697
|
+
return {
|
|
698
|
+
model: modelSpec === null ? null : projectModelRoute(modelSpec),
|
|
699
|
+
spawnModel: spawnModelSpec === null ? null : projectModelRoute(spawnModelSpec),
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
// {§worker-model-selection} — persist an explicit model selection onto the worker
|
|
703
|
+
// and return the resolved spec. An unresolvable selector fails loud here.
|
|
704
|
+
async setWorkerModel(args) {
|
|
705
|
+
const workspaceId = ClientInput.assertId("worker.model.set", "workspaceId", args.workspaceId);
|
|
706
|
+
const workerId = ClientInput.assertId("worker.model.set", "workerId", args.workerId);
|
|
707
|
+
await this.#assertWorkerOwned(workspaceId, workerId);
|
|
708
|
+
await this.#assertWorkerSelectable(workerId);
|
|
709
|
+
const selector = ClientInput.assertSelector("worker.model.set", "selector", args.selector);
|
|
710
|
+
const policy = await this.#resolveWorkerModel(workerId, selector);
|
|
711
|
+
if (policy === null) {
|
|
712
|
+
throw new OperationFailureError(Results.failure("daemon:provider", "not-configured", 501, "No provider is configured for this worker.", {}, { stage: "provider-selection", recovery: "Select a configured model provider.", retryable: false }));
|
|
713
|
+
}
|
|
714
|
+
return projectModelRoute(policy.providerSpec);
|
|
715
|
+
}
|
|
716
|
+
async #reasoningSupportForWorker(workerId, row, providerSpec, policy) {
|
|
717
|
+
const provider = await this.#providerForPolicy(providerSpec, policy);
|
|
718
|
+
if (row.spawn_model_route_id === null)
|
|
719
|
+
return provider.supportedReasoningPolicies;
|
|
720
|
+
const spawnSpec = await specForRoute(this.#db, row.spawn_model_route_id);
|
|
721
|
+
if (spawnSpec === null)
|
|
722
|
+
throw new Error(`worker ${workerId}: spawn model route is missing`);
|
|
723
|
+
const spawnProvider = await this.#providerForPolicy(spawnSpec, policy);
|
|
724
|
+
return provider.supportedReasoningPolicies.filter((candidate) => spawnProvider.supportedReasoningPolicies.includes(candidate));
|
|
725
|
+
}
|
|
726
|
+
// {§worker-reasoning-policy} — the worker's reasoning policy is durable and
|
|
727
|
+
// provider-relative. A model-less worker remains explicitly uninitialized.
|
|
728
|
+
async readWorkerReasoning(args) {
|
|
729
|
+
const workspaceId = ClientInput.assertId("worker.reasoning.get", "workspaceId", args.workspaceId);
|
|
730
|
+
const workerId = ClientInput.assertId("worker.reasoning.get", "workerId", args.workerId);
|
|
731
|
+
await this.#assertWorkerOwned(workspaceId, workerId);
|
|
732
|
+
await this.#resolveWorkerModel(workerId, undefined);
|
|
733
|
+
const row = await this.#db.worker_generation_policy_read.get({ id: workerId });
|
|
734
|
+
if (row === undefined)
|
|
735
|
+
throw new Error(`worker ${workerId}: generation policy row missing`);
|
|
736
|
+
if (row.model_route_id === null) {
|
|
737
|
+
if (row.reasoning_policy !== null) {
|
|
738
|
+
throw new Error(`worker ${workerId}: reasoning policy exists without a model route`);
|
|
739
|
+
}
|
|
740
|
+
return { policy: null, supportedPolicies: [] };
|
|
741
|
+
}
|
|
742
|
+
if (row.reasoning_policy === null) {
|
|
743
|
+
throw new Error(`worker ${workerId}: durable model has no reasoning policy`);
|
|
744
|
+
}
|
|
745
|
+
const spec = await specForRoute(this.#db, row.model_route_id);
|
|
746
|
+
if (spec === null)
|
|
747
|
+
throw new Error(`worker ${workerId}: model route is missing`);
|
|
748
|
+
return {
|
|
749
|
+
policy: row.reasoning_policy,
|
|
750
|
+
supportedPolicies: await this.#reasoningSupportForWorker(workerId, row, spec, row.reasoning_policy),
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
// {§worker-reasoning-policy} — mutate between loops, validate against both
|
|
754
|
+
// the worker model and its optional spawn model, then persist atomically.
|
|
755
|
+
async setWorkerReasoning(args) {
|
|
756
|
+
const workspaceId = ClientInput.assertId("worker.reasoning.set", "workspaceId", args.workspaceId);
|
|
757
|
+
const workerId = ClientInput.assertId("worker.reasoning.set", "workerId", args.workerId);
|
|
758
|
+
let policy;
|
|
759
|
+
try {
|
|
760
|
+
policy = Validator.assertReasoningPolicy(args.policy);
|
|
761
|
+
}
|
|
762
|
+
catch (cause) {
|
|
763
|
+
throw daemonFailure("daemon:input", "reasoning-policy-invalid", 400, cause instanceof Error ? cause.message : "The reasoning policy is invalid.", { field: "policy", retryable: false });
|
|
764
|
+
}
|
|
765
|
+
await this.#assertWorkerOwned(workspaceId, workerId);
|
|
766
|
+
await this.#assertWorkerSelectable(workerId);
|
|
767
|
+
await this.#resolveWorkerModel(workerId, undefined);
|
|
768
|
+
const row = await this.#db.worker_generation_policy_read.get({ id: workerId });
|
|
769
|
+
if (row === undefined)
|
|
770
|
+
throw new Error(`worker ${workerId}: generation policy row missing`);
|
|
771
|
+
const spec = await specForRoute(this.#db, row.model_route_id);
|
|
772
|
+
if (spec === null) {
|
|
773
|
+
throw daemonFailure("daemon:provider", "worker-model-unset", 409, `Worker ${workerId} has no model against which to validate a reasoning policy.`, {
|
|
774
|
+
workerId,
|
|
775
|
+
stage: "provider-selection",
|
|
776
|
+
recovery: "Select a model before selecting its reasoning policy.",
|
|
777
|
+
retryable: false,
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
const supportedPolicies = await this.#reasoningSupportForWorker(workerId, row, spec, policy);
|
|
781
|
+
await this.#db.worker_generation_policy_update.run({
|
|
782
|
+
id: workerId,
|
|
783
|
+
model_route_id: row.model_route_id,
|
|
784
|
+
spawn_model_route_id: row.spawn_model_route_id,
|
|
785
|
+
reasoning_policy: policy,
|
|
786
|
+
});
|
|
787
|
+
return { policy, supportedPolicies };
|
|
788
|
+
}
|
|
789
|
+
// {§worker-model-selection} — persist the worker's spawn override; a null
|
|
790
|
+
// selector means inherit (clears the override).
|
|
791
|
+
async setWorkerSpawnModel(args) {
|
|
792
|
+
const workspaceId = ClientInput.assertId("worker.child.set", "workspaceId", args.workspaceId);
|
|
793
|
+
const workerId = ClientInput.assertId("worker.child.set", "workerId", args.workerId);
|
|
794
|
+
await this.#assertWorkerOwned(workspaceId, workerId);
|
|
795
|
+
await this.#assertWorkerSelectable(workerId);
|
|
796
|
+
const selector = ClientInput.assertChildSelector("worker.child.set", args.selector);
|
|
797
|
+
const spec = await this.#resolveWorkerSpawnModel(workerId, selector);
|
|
798
|
+
return spec === null ? null : projectModelRoute(spec);
|
|
799
|
+
}
|
|
800
|
+
// {§worker-model-selection} — a selection must not mutate underneath active
|
|
801
|
+
// work: a live or parked loop is a precise 409, never a silent retroactive
|
|
802
|
+
// switch of the immutable loop snapshot.
|
|
803
|
+
async #assertWorkerSelectable(workerId) {
|
|
804
|
+
if (await this.#drains.hasLiveWork(workerId)) {
|
|
805
|
+
throw daemonFailure("daemon:worker", "worker-loop-active", 409, `Worker ${workerId} has a live or parked loop; select a model after concluding or cancelling it.`, {
|
|
806
|
+
workerId,
|
|
807
|
+
stage: "model-selection",
|
|
808
|
+
recovery: "Conclude or cancel the active loop before selecting a model.",
|
|
809
|
+
retryable: false,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
async #assertWorkerOwned(workspaceId, workerId) {
|
|
814
|
+
const owner = await this.#db.envelope_get_worker_by_id.get({ id: workerId });
|
|
815
|
+
if (owner === undefined) {
|
|
816
|
+
throw daemonFailure("daemon:worker", "worker-not-found", 404, `Worker ${workerId} does not exist.`, { workerId });
|
|
817
|
+
}
|
|
818
|
+
if (owner.workspace_id !== workspaceId) {
|
|
819
|
+
throw daemonFailure("daemon:worker", "workspace-mismatch", 409, `Worker ${workerId} does not belong to workspace ${workspaceId}.`, { workerId, workspaceId, actualWorkspaceId: owner.workspace_id });
|
|
820
|
+
}
|
|
467
821
|
}
|
|
468
|
-
// {§methods-op-mirror} — execute parsed ops on behalf of a client
|
|
822
|
+
// {§methods-op-mirror} — execute parsed ops on behalf of a client as a
|
|
469
823
|
// client-origin turn (the log is core's, a client op is a first-class citizen), dispatched through
|
|
470
824
|
// the engine, then emitted as log/entry on the event source. One seam op backs the whole op_*
|
|
471
825
|
// family (read/edit/copy/find/fold/look/move/open/send/exec); the module parses at its edge with the
|
|
@@ -485,10 +839,10 @@ export default class Daemon {
|
|
|
485
839
|
throw error;
|
|
486
840
|
}
|
|
487
841
|
}
|
|
488
|
-
// The client-interface action contract: one AG-UI action owns one
|
|
489
|
-
// regardless of how many statements op.parse produced.
|
|
490
|
-
// keep
|
|
491
|
-
//
|
|
842
|
+
// The client-interface action contract: one AG-UI action owns one administrative
|
|
843
|
+
// loop, regardless of how many statements op.parse produced. Each statement is one
|
|
844
|
+
// ordinary operation turn; a proposal may keep that turn and loop open across
|
|
845
|
+
// interrupt/resume until settlement.
|
|
492
846
|
async dispatchClientAction(args) {
|
|
493
847
|
const workspaceId = ClientInput.assertId("operation.dispatch-batch", "workspaceId", args.workspaceId);
|
|
494
848
|
const workerId = ClientInput.assertId("operation.dispatch-batch", "workerId", args.workerId);
|
|
@@ -513,18 +867,38 @@ export default class Daemon {
|
|
|
513
867
|
const { workspaceId, workerId, loopId, statement } = args;
|
|
514
868
|
const release = await this.#workspaceGate.acquireTurn(workspaceId, workerId);
|
|
515
869
|
try {
|
|
516
|
-
const { id: turnId } = await
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
origin: "client", onDispatch: (logEntryId) => { entryIds.push(logEntryId); },
|
|
870
|
+
const { id: turnId } = await Turn.open(this.#db, {
|
|
871
|
+
loopId,
|
|
872
|
+
producer: "client",
|
|
873
|
+
kind: "operation",
|
|
521
874
|
});
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const
|
|
525
|
-
|
|
875
|
+
let turnOpen = true;
|
|
876
|
+
try {
|
|
877
|
+
const entryIds = [];
|
|
878
|
+
const result = await this.#engine.dispatch({
|
|
879
|
+
statement, workspaceId, workerId, loopId, turnId, sequence: 1,
|
|
880
|
+
origin: "client", onDispatch: (logEntryId) => { entryIds.push(logEntryId); },
|
|
881
|
+
});
|
|
882
|
+
await Turn.complete(this.#db, turnId, result.status);
|
|
883
|
+
turnOpen = false;
|
|
884
|
+
await this.#branchBatches.sealTurn(turnId);
|
|
885
|
+
for (const logEntryId of entryIds) {
|
|
886
|
+
const entry = await LogEntry.fetchLogEntry(this.#db, logEntryId);
|
|
887
|
+
this.#broadcast({ workspaceId }, "log/entry", { entry });
|
|
888
|
+
}
|
|
889
|
+
return result;
|
|
890
|
+
}
|
|
891
|
+
catch (cause) {
|
|
892
|
+
if (turnOpen) {
|
|
893
|
+
try {
|
|
894
|
+
await Turn.complete(this.#db, turnId, 500);
|
|
895
|
+
}
|
|
896
|
+
catch (completionCause) {
|
|
897
|
+
throw new AggregateError([cause, completionCause], `client operation turn ${turnId} failed and could not be completed`);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
throw cause;
|
|
526
901
|
}
|
|
527
|
-
return result;
|
|
528
902
|
}
|
|
529
903
|
finally {
|
|
530
904
|
release();
|
|
@@ -616,7 +990,7 @@ export default class Daemon {
|
|
|
616
990
|
// {§methods} — the module's render surface beyond the journal. Thin delegations
|
|
617
991
|
// into core's envelope / membership / provider machinery; the module fans the results into its own views.
|
|
618
992
|
listProviders() {
|
|
619
|
-
const active =
|
|
993
|
+
const active = resolveActiveRoute();
|
|
620
994
|
return {
|
|
621
995
|
aliases: parseAliasesFromEnv().map((a) => {
|
|
622
996
|
const isActive = active !== null && active.alias === a.alias;
|
|
@@ -627,6 +1001,11 @@ export default class Daemon {
|
|
|
627
1001
|
}),
|
|
628
1002
|
};
|
|
629
1003
|
}
|
|
1004
|
+
// {§model-catalog} The catalog is a bounded, worldless local projection.
|
|
1005
|
+
// It performs no provider I/O and never changes the worker's selection.
|
|
1006
|
+
listModels(query) {
|
|
1007
|
+
return listModelCatalog(query);
|
|
1008
|
+
}
|
|
630
1009
|
// {§client-display-capabilities} Core composes the installed family
|
|
631
1010
|
// declarations; interface modules expose this contracts-owned wire without
|
|
632
1011
|
// inventing presentation policy. `exec` is operation machinery, not an
|
|
@@ -699,7 +1078,11 @@ export default class Daemon {
|
|
|
699
1078
|
}
|
|
700
1079
|
if (constraints.length > 0)
|
|
701
1080
|
await GitMembership.resolveGitMembership(this.#db, envelope.workspaceId, undefined);
|
|
1081
|
+
for (const provider of this.#workspaceCapabilityProviders.values()) {
|
|
1082
|
+
await provider.hydrate(envelope.workspaceId);
|
|
1083
|
+
}
|
|
702
1084
|
await LoopDocs.materialize(this.#engine, this.#db, envelope.workspaceId);
|
|
1085
|
+
await SkillDocs.materialize(this.#engine, this.#db, envelope.workspaceId);
|
|
703
1086
|
void this.#engine.warmWorkspaceDerivations(envelope.workspaceId).catch(() => { });
|
|
704
1087
|
this.#broadcast("all", "workspace/created", { id: envelope.workspaceId, name: envelope.workspaceName, projectRoot: envelope.projectRoot });
|
|
705
1088
|
return envelope;
|
|
@@ -898,6 +1281,9 @@ export default class Daemon {
|
|
|
898
1281
|
}
|
|
899
1282
|
}
|
|
900
1283
|
const worker = await Envelope.createModelWorker(this.#db, workspaceId, name);
|
|
1284
|
+
if (args.settings !== undefined) {
|
|
1285
|
+
await this.#mergeWorkerSettings(worker.id, args.settings);
|
|
1286
|
+
}
|
|
901
1287
|
return { workerId: worker.id, workerName: worker.name };
|
|
902
1288
|
}
|
|
903
1289
|
// {§worker-scheme-fork} — branch a worker's log while sharing the workspace world.
|
|
@@ -928,50 +1314,200 @@ export default class Daemon {
|
|
|
928
1314
|
const branch = await this.#db.envelope_get_worker_by_id.get({ id: branchWorkerId });
|
|
929
1315
|
return { workerId: branchWorkerId, workerName: branch?.name ?? null, parentWorkerId: workerId };
|
|
930
1316
|
}
|
|
931
|
-
async
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
935
|
-
const runtime = RuntimeDeclaration.assert(decl, namespaceOwner);
|
|
936
|
-
this.#engine.registerRuntime(runtime.name, {
|
|
937
|
-
executor,
|
|
938
|
-
namespaceOwner: { kind: "module", name: namespaceOwner },
|
|
939
|
-
glyph: runtime.glyph ?? "",
|
|
940
|
-
invocation: runtime.invocation,
|
|
941
|
-
documentation: runtime.documentation ?? "",
|
|
942
|
-
available: availability.available,
|
|
943
|
-
detail: availability.detail,
|
|
944
|
-
}, scheme);
|
|
1317
|
+
async registerRuntimes(registrations) {
|
|
1318
|
+
const normalized = registrations.map((registration) => this.#normalizeRuntime(registration));
|
|
1319
|
+
this.#engine.registerRuntimes(normalized);
|
|
945
1320
|
if (this.#capabilitiesPublished) {
|
|
946
1321
|
for (const workspace of await Envelope.listWorkspaces(this.#db)) {
|
|
947
1322
|
await LoopDocs.materialize(this.#engine, this.#db, workspace.id);
|
|
1323
|
+
await SkillDocs.materialize(this.#engine, this.#db, workspace.id);
|
|
948
1324
|
}
|
|
949
1325
|
}
|
|
950
1326
|
}
|
|
1327
|
+
async registerRuntime(registration) {
|
|
1328
|
+
await this.registerRuntimes([registration]);
|
|
1329
|
+
}
|
|
951
1330
|
async registerScheme(name, handler) {
|
|
952
1331
|
this.#schemes.register(name, handler);
|
|
953
1332
|
if (this.#capabilitiesPublished) {
|
|
954
1333
|
await this.#schemes.ready();
|
|
955
1334
|
for (const workspace of await Envelope.listWorkspaces(this.#db)) {
|
|
956
1335
|
await LoopDocs.materialize(this.#engine, this.#db, workspace.id);
|
|
1336
|
+
await SkillDocs.materialize(this.#engine, this.#db, workspace.id);
|
|
957
1337
|
}
|
|
958
1338
|
}
|
|
959
1339
|
}
|
|
960
|
-
|
|
1340
|
+
#normalizeRuntime({ namespaceOwner, decl, executor, availability, scheme }) {
|
|
1341
|
+
if (typeof namespaceOwner !== "string" || namespaceOwner.trim().length === 0) {
|
|
1342
|
+
throw new Error("runtime registration namespaceOwner must be a non-empty string");
|
|
1343
|
+
}
|
|
1344
|
+
const runtime = RuntimeDeclaration.assert(decl, namespaceOwner);
|
|
1345
|
+
return {
|
|
1346
|
+
tag: runtime.name,
|
|
1347
|
+
entry: {
|
|
1348
|
+
executor,
|
|
1349
|
+
namespaceOwner: { kind: "module", name: namespaceOwner },
|
|
1350
|
+
glyph: runtime.glyph ?? "",
|
|
1351
|
+
summary: runtime.summary,
|
|
1352
|
+
invocation: runtime.invocation,
|
|
1353
|
+
details: runtime.details ?? "",
|
|
1354
|
+
...(runtime.resourcesPath === undefined ? {} : { resourcesPath: runtime.resourcesPath }),
|
|
1355
|
+
...(runtime.expandTools === undefined ? {} : { expandTools: runtime.expandTools }),
|
|
1356
|
+
available: availability.available,
|
|
1357
|
+
detail: availability.detail,
|
|
1358
|
+
},
|
|
1359
|
+
scheme,
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
registerModuleAction(registration) {
|
|
1363
|
+
const { name, scope, handler } = registration;
|
|
961
1364
|
if (name.length === 0)
|
|
962
1365
|
throw new Error("registerModuleAction: action name must not be empty");
|
|
1366
|
+
if (scope !== "worldless" && scope !== "workspace") {
|
|
1367
|
+
throw new Error(`module action '${name}' has invalid scope '${String(scope)}'`);
|
|
1368
|
+
}
|
|
1369
|
+
if (typeof handler !== "function")
|
|
1370
|
+
throw new Error(`module action '${name}' has no handler`);
|
|
963
1371
|
if (this.#moduleActions.has(name))
|
|
964
1372
|
throw new Error(`module action '${name}' is already registered`);
|
|
965
|
-
this.#moduleActions.set(name,
|
|
1373
|
+
this.#moduleActions.set(name, registration);
|
|
966
1374
|
}
|
|
967
1375
|
listModuleActions() {
|
|
968
|
-
return [...this.#moduleActions.
|
|
1376
|
+
return [...this.#moduleActions.values()]
|
|
1377
|
+
.map(({ name, scope }) => ({ name, scope }))
|
|
1378
|
+
.toSorted((left, right) => left.name.localeCompare(right.name));
|
|
969
1379
|
}
|
|
970
|
-
async invokeModuleAction(name, params) {
|
|
971
|
-
const
|
|
972
|
-
if (
|
|
1380
|
+
async invokeModuleAction(name, params, context) {
|
|
1381
|
+
const registration = this.#moduleActions.get(name);
|
|
1382
|
+
if (registration === undefined)
|
|
973
1383
|
throw new Error(`module action '${name}' is not registered`);
|
|
974
|
-
|
|
1384
|
+
if (registration.scope !== context.scope) {
|
|
1385
|
+
throw new Error(`module action '${name}' requires ${registration.scope} context, not ${context.scope}`);
|
|
1386
|
+
}
|
|
1387
|
+
if (context.scope === "workspace") {
|
|
1388
|
+
ClientInput.assertId(`module action '${name}'`, "workspaceId", context.workspaceId);
|
|
1389
|
+
}
|
|
1390
|
+
return registration.handler(params, context);
|
|
1391
|
+
}
|
|
1392
|
+
registerWorkspaceCapabilityProvider(namespaceOwner, provider) {
|
|
1393
|
+
if (namespaceOwner.trim().length === 0) {
|
|
1394
|
+
throw new Error("workspace capability provider requires a non-empty namespace owner");
|
|
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}'`);
|
|
1430
|
+
}
|
|
1431
|
+
return this.#normalizeRuntime(registration);
|
|
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
|
+
});
|
|
1441
|
+
}
|
|
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
|
+
try {
|
|
1450
|
+
const commitRuntimes = await this.#engine.prepareWorkspaceRuntimes(checkedWorkspaceId, namespaceOwner, normalized);
|
|
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;
|
|
1507
|
+
}
|
|
1508
|
+
finally {
|
|
1509
|
+
gate.release();
|
|
1510
|
+
}
|
|
975
1511
|
}
|
|
976
1512
|
get engine() { return this.#engine; }
|
|
977
1513
|
get provider() { return this.#provider; }
|
|
@@ -998,6 +1534,23 @@ export default class Daemon {
|
|
|
998
1534
|
// shell is the default runtime, so its executor must boot usable.
|
|
999
1535
|
const executors = await ExecutorRegistry.build({ defaultRuntime: "sh", cwd: this.#discoveryCwd });
|
|
1000
1536
|
this.#engine.setExecutors(executors);
|
|
1537
|
+
// {§question-tool} — the native request-user-input runtime, process-wide;
|
|
1538
|
+
// per-worker admission gates its doc visibility and dispatch ({§worker-settings}).
|
|
1539
|
+
await this.#engine.registerRuntimes([{
|
|
1540
|
+
tag: "question",
|
|
1541
|
+
entry: {
|
|
1542
|
+
executor: new QuestionTool({ runtime: "question", glyph: "❓" }),
|
|
1543
|
+
namespaceOwner: { kind: "module", name: "core" },
|
|
1544
|
+
glyph: "❓",
|
|
1545
|
+
summary: questionRuntimeDecl.summary,
|
|
1546
|
+
invocation: questionRuntimeDecl.invocation,
|
|
1547
|
+
details: questionRuntimeDecl.details ?? "",
|
|
1548
|
+
available: true,
|
|
1549
|
+
detail: "in-process",
|
|
1550
|
+
},
|
|
1551
|
+
}]);
|
|
1552
|
+
// {§effect-policy-tunable} — invalid operator policy fails boot, not the first EXEC.
|
|
1553
|
+
EffectPolicy.validateConfiguration();
|
|
1001
1554
|
// {§exec} — mint a scheme per runtime tag so exec output entries address by tag
|
|
1002
1555
|
// authority (sh:///l/t/s). The "exec" scheme stays for the EXEC op dispatch.
|
|
1003
1556
|
this.#schemes.registerRuntimeSchemes(executors);
|
|
@@ -1005,18 +1558,34 @@ export default class Daemon {
|
|
|
1005
1558
|
// (agnostic, by plurnk.kind:"scheme"). They light up http://, etc. with
|
|
1006
1559
|
// no further engine change — #run wraps their context in SchemeCtxImpl ({§plugin-discovery}).
|
|
1007
1560
|
await this.#schemes.discoverExternal(this.#discoveryCwd);
|
|
1561
|
+
// {§module-discovery} — third-party daemon-module composition: trusted
|
|
1562
|
+
// packages declaring `plurnk.kind: "module"` register beside the
|
|
1563
|
+
// service's explicit composition before any module setup runs.
|
|
1564
|
+
const discoveredModules = await discoverDaemonModules({ cwd: this.#discoveryCwd });
|
|
1565
|
+
for (const packageName of discoveredModules.skipped) {
|
|
1566
|
+
console.warn(`module discovery: '${packageName}' is discovered but untrusted (PLURNK_PLUGINS_TRUSTED_ONLY); not registered`);
|
|
1567
|
+
}
|
|
1568
|
+
for (const module of discoveredModules.modules) {
|
|
1569
|
+
this.#modules.push(module);
|
|
1570
|
+
}
|
|
1008
1571
|
const setupSeam = this;
|
|
1009
1572
|
for (const module of this.#modules) {
|
|
1010
1573
|
if (module.close !== undefined)
|
|
1011
1574
|
this.#moduleClosers.push(module);
|
|
1012
1575
|
await module.setup?.(setupSeam);
|
|
1013
1576
|
}
|
|
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
|
+
}
|
|
1014
1582
|
await this.#schemes.ready();
|
|
1015
1583
|
// Reconcile the kernel-published documentation surface once per existing workspace.
|
|
1016
1584
|
// Installed capabilities and operator configuration are now fully known; model loops
|
|
1017
1585
|
// consume this workspace state but never republish it.
|
|
1018
1586
|
for (const workspace of await Envelope.listWorkspaces(this.#db)) {
|
|
1019
1587
|
await LoopDocs.materialize(this.#engine, this.#db, workspace.id);
|
|
1588
|
+
await SkillDocs.materialize(this.#engine, this.#db, workspace.id);
|
|
1020
1589
|
}
|
|
1021
1590
|
this.#capabilitiesPublished = true;
|
|
1022
1591
|
await this.#recoverLifecycle();
|
|
@@ -1033,7 +1602,9 @@ export default class Daemon {
|
|
|
1033
1602
|
await this.#db.recovery_fail_active_loops.run({});
|
|
1034
1603
|
await this.#db.recovery_settle_open_provider_requests.run({});
|
|
1035
1604
|
await this.#db.recovery_fail_open_model_calls.run({});
|
|
1605
|
+
await this.#db.recovery_fail_open_turns.run({});
|
|
1036
1606
|
await this.#db.recovery_fail_ownerless_proposals.run({});
|
|
1607
|
+
await this.#db.recovery_remove_ownerless_client_interactions.run({});
|
|
1037
1608
|
await this.#db.recovery_error_orphan_subscription_channels.run({});
|
|
1038
1609
|
await this.#db.recovery_fail_orphan_subscriptions.run({});
|
|
1039
1610
|
await this.#db.recovery_resume_unblocked_parks.run({});
|
|
@@ -1059,10 +1630,34 @@ export default class Daemon {
|
|
|
1059
1630
|
await this.#drains.schedulePollWake(row.workspace_id, row.worker_id, systemPrompt);
|
|
1060
1631
|
}
|
|
1061
1632
|
}
|
|
1633
|
+
// {§crash-only-stop} — the settle deadline. Default 30s; an operator can
|
|
1634
|
+
// raise it for slow hosts or lower it for tests. 0 is refused (an unbounded
|
|
1635
|
+
// stop is exactly the wedge this exists to prevent).
|
|
1636
|
+
static #stopDeadlineMs() {
|
|
1637
|
+
const raw = Number(process.env.PLURNK_SERVICE_STOP_TIMEOUT_MS ?? 30_000);
|
|
1638
|
+
if (!Number.isSafeInteger(raw) || raw <= 0) {
|
|
1639
|
+
throw new Error(`PLURNK_SERVICE_STOP_TIMEOUT_MS must be a positive integer; got ${JSON.stringify(process.env.PLURNK_SERVICE_STOP_TIMEOUT_MS)}.`);
|
|
1640
|
+
}
|
|
1641
|
+
return raw;
|
|
1642
|
+
}
|
|
1062
1643
|
async stop() {
|
|
1063
1644
|
if (!this.#started)
|
|
1064
1645
|
return;
|
|
1065
1646
|
this.#started = false;
|
|
1647
|
+
const stopDeadlineMs = Daemon.#stopDeadlineMs();
|
|
1648
|
+
const deadline = Date.now() + stopDeadlineMs;
|
|
1649
|
+
const settle = (label, wait) => new Promise((resolve) => {
|
|
1650
|
+
let settled = false;
|
|
1651
|
+
const finish = (result) => {
|
|
1652
|
+
if (settled)
|
|
1653
|
+
return;
|
|
1654
|
+
settled = true;
|
|
1655
|
+
clearTimeout(timer);
|
|
1656
|
+
resolve(result);
|
|
1657
|
+
};
|
|
1658
|
+
const timer = setTimeout(() => finish({ status: "rejected", reason: new Error(`stop deadline exceeded waiting for ${label}`) }), Math.max(0, deadline - Date.now()));
|
|
1659
|
+
Promise.resolve().then(wait).then((value) => finish({ status: "fulfilled", value }), (reason) => finish({ status: "rejected", reason }));
|
|
1660
|
+
});
|
|
1066
1661
|
// Stop accepting external work immediately, but do not await listener
|
|
1067
1662
|
// closure before cancelling active workers: an SSE connection may itself be
|
|
1068
1663
|
// waiting for the worker cancellation that follows.
|
|
@@ -1087,27 +1682,35 @@ export default class Daemon {
|
|
|
1087
1682
|
this.#engine.cancelDerivations(derivationAbort);
|
|
1088
1683
|
this.#branchBatches.beginStop();
|
|
1089
1684
|
this.#drains.beginStop("daemon_stopping");
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1685
|
+
// {§crash-only-stop} — the settle sequence is DEADLINE-BOUNDED: a child
|
|
1686
|
+
// that never closes (a wedged MCP server, a stuck stream) must not hang
|
|
1687
|
+
// the daemon forever. Past the deadline the waits are abandoned; the
|
|
1688
|
+
// process may exit with the WAL in place (SQLite recovers) rather than
|
|
1689
|
+
// leak as a live-but-wedged tree.
|
|
1690
|
+
const branchResult = await settle("branch batches idle", () => this.#branchBatches.idle());
|
|
1691
|
+
const drainResult = await settle("drains idle", () => this.#drains.idle());
|
|
1692
|
+
const moduleResult = await settle("modules close", async () => {
|
|
1693
|
+
const results = await moduleClose;
|
|
1694
|
+
if (results.some((r) => r.status === "rejected"))
|
|
1695
|
+
throw new AggregateError(results.filter((r) => r.status === "rejected").map((r) => r.reason), "module close failed");
|
|
1696
|
+
});
|
|
1697
|
+
const streamingResult = await settle("streaming schemes idle", () => this.#drainStreamingSchemes());
|
|
1698
|
+
const derivationResult = await settle("derivation drain", () => this.#engine.drainDerivations(derivationAbort));
|
|
1699
|
+
const mimetypeResult = this.#ownsMimetypes
|
|
1700
|
+
? await settle("mimetypes dispose", () => this.#mimetypes.dispose())
|
|
1701
|
+
: null;
|
|
1702
|
+
const schemeResult = await settle("schemes close", () => this.#schemes.close());
|
|
1101
1703
|
// Streaming and scheme closure are the last producers of synchronous
|
|
1102
1704
|
// conclusion notifications. Join the supervisor-owned async tails only
|
|
1103
1705
|
// after those producers settle, before the caller may close SQLite.
|
|
1104
|
-
const
|
|
1706
|
+
const wakeResult = await settle("drains idle (wake)", () => this.#drains.idle());
|
|
1105
1707
|
const closeErrors = [
|
|
1106
|
-
|
|
1708
|
+
moduleResult,
|
|
1709
|
+
branchResult,
|
|
1107
1710
|
drainResult,
|
|
1108
1711
|
streamingResult,
|
|
1109
1712
|
derivationResult,
|
|
1110
|
-
...
|
|
1713
|
+
...(mimetypeResult === null ? [] : [mimetypeResult]),
|
|
1111
1714
|
schemeResult,
|
|
1112
1715
|
wakeResult,
|
|
1113
1716
|
]
|
|
@@ -1179,8 +1782,9 @@ export default class Daemon {
|
|
|
1179
1782
|
sequence: seqRow.next,
|
|
1180
1783
|
prompt: first.body,
|
|
1181
1784
|
flags: first.flags,
|
|
1182
|
-
|
|
1183
|
-
|
|
1785
|
+
model_route_id: first.model_route_id,
|
|
1786
|
+
spawn_model_route_id: first.spawn_model_route_id,
|
|
1787
|
+
reasoning_policy: first.reasoning_policy,
|
|
1184
1788
|
max_turns: first.max_turns,
|
|
1185
1789
|
open_paths: first.open_paths ?? "[]",
|
|
1186
1790
|
orphan_source_loop_id: endedLoopId,
|