@plurnk/plurnk-service 0.7.0 → 0.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/SPEC.md +116 -83
- package/bin/plurnk-service.ts +132 -0
- package/dist/Paths.d.ts +8 -0
- package/dist/Paths.d.ts.map +1 -0
- package/dist/Paths.js +47 -0
- package/dist/Paths.js.map +1 -0
- package/dist/content/index.d.ts +9 -0
- package/dist/content/index.d.ts.map +1 -0
- package/dist/content/index.js +10 -0
- package/dist/content/index.js.map +1 -0
- package/dist/content/line-marker.d.ts +26 -0
- package/dist/content/line-marker.d.ts.map +1 -0
- package/dist/content/line-marker.js +323 -0
- package/dist/content/line-marker.js.map +1 -0
- package/dist/content/matcher.d.ts +15 -0
- package/dist/content/matcher.d.ts.map +1 -0
- package/dist/content/matcher.js +112 -0
- package/dist/content/matcher.js.map +1 -0
- package/dist/content/mimetype-binary.d.ts +9 -0
- package/dist/content/mimetype-binary.d.ts.map +1 -0
- package/dist/content/mimetype-binary.js +86 -0
- package/dist/content/mimetype-binary.js.map +1 -0
- package/dist/content/path-mimetype.d.ts +6 -0
- package/dist/content/path-mimetype.d.ts.map +1 -0
- package/dist/content/path-mimetype.js +49 -0
- package/dist/content/path-mimetype.js.map +1 -0
- package/dist/content/read-resolve.d.ts +20 -0
- package/dist/content/read-resolve.d.ts.map +1 -0
- package/dist/content/read-resolve.js +60 -0
- package/dist/content/read-resolve.js.map +1 -0
- package/dist/core/ChannelWrite.d.ts +35 -30
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +49 -41
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/Engine.d.ts +16 -10
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +309 -115
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/EnvFlags.d.ts +6 -3
- package/dist/core/EnvFlags.d.ts.map +1 -1
- package/dist/core/EnvFlags.js +62 -60
- package/dist/core/EnvFlags.js.map +1 -1
- package/dist/core/ExecutorRegistry.d.ts +26 -0
- package/dist/core/ExecutorRegistry.d.ts.map +1 -0
- package/dist/core/ExecutorRegistry.js +99 -0
- package/dist/core/ExecutorRegistry.js.map +1 -0
- package/dist/core/PluginLoader.d.ts +6 -3
- package/dist/core/PluginLoader.d.ts.map +1 -1
- package/dist/core/PluginLoader.js +77 -73
- package/dist/core/PluginLoader.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts +4 -2
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +23 -22
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts +1 -1
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +3 -3
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/git-membership.d.ts +8 -0
- package/dist/core/git-membership.d.ts.map +1 -0
- package/dist/core/git-membership.js +125 -0
- package/dist/core/git-membership.js.map +1 -0
- package/dist/core/packet-wire.d.ts +47 -6
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +376 -312
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/resolveForLoop.d.ts +16 -0
- package/dist/core/resolveForLoop.d.ts.map +1 -0
- package/dist/core/resolveForLoop.js +34 -0
- package/dist/core/resolveForLoop.js.map +1 -0
- package/dist/core/results.d.ts +40 -0
- package/dist/core/results.d.ts.map +1 -0
- package/dist/core/results.js +52 -0
- package/dist/core/results.js.map +1 -0
- package/dist/core/scheme-types.d.ts +7 -4
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/scheme-types.js +4 -4
- package/dist/core/scheme-types.js.map +1 -1
- package/dist/core/types.d.ts +26 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +17 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +1 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -43
- package/dist/index.js.map +1 -1
- package/dist/schemes/EffectPolicy.d.ts +6 -0
- package/dist/schemes/EffectPolicy.d.ts.map +1 -0
- package/dist/schemes/EffectPolicy.js +18 -0
- package/dist/schemes/EffectPolicy.js.map +1 -0
- package/dist/schemes/Exec.d.ts +1 -0
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +86 -45
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/File.d.ts +0 -1
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +32 -69
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Known.d.ts.map +1 -1
- package/dist/schemes/Known.js +13 -13
- package/dist/schemes/Known.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +8 -52
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Plurnk.d.ts.map +1 -1
- package/dist/schemes/Plurnk.js +13 -13
- package/dist/schemes/Plurnk.js.map +1 -1
- package/dist/schemes/Skill.d.ts.map +1 -1
- package/dist/schemes/Skill.js +13 -13
- package/dist/schemes/Skill.js.map +1 -1
- package/dist/schemes/Unknown.d.ts.map +1 -1
- package/dist/schemes/Unknown.js +13 -13
- package/dist/schemes/Unknown.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts +5 -3
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +55 -50
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-find.d.ts +10 -3
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +99 -77
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-manifest.d.ts +6 -0
- package/dist/schemes/_entry-manifest.d.ts.map +1 -0
- package/dist/schemes/_entry-manifest.js +45 -0
- package/dist/schemes/_entry-manifest.js.map +1 -0
- package/dist/schemes/_entry-ops.d.ts +7 -4
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +198 -316
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-send.d.ts +4 -1
- package/dist/schemes/_entry-send.d.ts.map +1 -1
- package/dist/schemes/_entry-send.js +57 -55
- package/dist/schemes/_entry-send.js.map +1 -1
- package/dist/server/ClientConnection.js +3 -3
- package/dist/server/ClientConnection.js.map +1 -1
- package/dist/server/Daemon.d.ts +5 -5
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +234 -176
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/clientTurn.d.ts +4 -1
- package/dist/server/clientTurn.d.ts.map +1 -1
- package/dist/server/clientTurn.js +19 -17
- package/dist/server/clientTurn.js.map +1 -1
- package/dist/server/dsl.d.ts +19 -16
- package/dist/server/dsl.d.ts.map +1 -1
- package/dist/server/dsl.js +127 -105
- package/dist/server/dsl.js.map +1 -1
- package/dist/server/envelope.d.ts +22 -19
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +116 -102
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/logEntry.d.ts +4 -1
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +41 -39
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/methods/_dispatchAsClient.d.ts +3 -1
- package/dist/server/methods/_dispatchAsClient.d.ts.map +1 -1
- package/dist/server/methods/_dispatchAsClient.js +31 -29
- package/dist/server/methods/_dispatchAsClient.js.map +1 -1
- package/dist/server/methods/discover.d.ts +3 -1
- package/dist/server/methods/discover.d.ts.map +1 -1
- package/dist/server/methods/discover.js +8 -6
- package/dist/server/methods/discover.js.map +1 -1
- package/dist/server/methods/entry_read.d.ts +4 -1
- package/dist/server/methods/entry_read.d.ts.map +1 -1
- package/dist/server/methods/entry_read.js +54 -52
- package/dist/server/methods/entry_read.js.map +1 -1
- package/dist/server/methods/log_read.d.ts +4 -1
- package/dist/server/methods/log_read.d.ts.map +1 -1
- package/dist/server/methods/log_read.js +38 -36
- package/dist/server/methods/log_read.js.map +1 -1
- package/dist/server/methods/loop_cancel.d.ts +3 -1
- package/dist/server/methods/loop_cancel.d.ts.map +1 -1
- package/dist/server/methods/loop_cancel.js +19 -17
- package/dist/server/methods/loop_cancel.js.map +1 -1
- package/dist/server/methods/loop_resolve.d.ts +3 -1
- package/dist/server/methods/loop_resolve.d.ts.map +1 -1
- package/dist/server/methods/loop_resolve.js +42 -40
- package/dist/server/methods/loop_resolve.js.map +1 -1
- package/dist/server/methods/loop_run.d.ts +3 -1
- package/dist/server/methods/loop_run.d.ts.map +1 -1
- package/dist/server/methods/loop_run.js +104 -102
- package/dist/server/methods/loop_run.js.map +1 -1
- package/dist/server/methods/op_copy.d.ts +3 -1
- package/dist/server/methods/op_copy.d.ts.map +1 -1
- package/dist/server/methods/op_copy.js +25 -23
- package/dist/server/methods/op_copy.js.map +1 -1
- package/dist/server/methods/op_dispatch.d.ts +3 -1
- package/dist/server/methods/op_dispatch.d.ts.map +1 -1
- package/dist/server/methods/op_dispatch.js +18 -16
- package/dist/server/methods/op_dispatch.js.map +1 -1
- package/dist/server/methods/op_edit.d.ts +3 -1
- package/dist/server/methods/op_edit.d.ts.map +1 -1
- package/dist/server/methods/op_edit.js +23 -21
- package/dist/server/methods/op_edit.js.map +1 -1
- package/dist/server/methods/op_exec.d.ts +3 -1
- package/dist/server/methods/op_exec.d.ts.map +1 -1
- package/dist/server/methods/op_exec.js +20 -18
- package/dist/server/methods/op_exec.js.map +1 -1
- package/dist/server/methods/op_find.d.ts +3 -1
- package/dist/server/methods/op_find.d.ts.map +1 -1
- package/dist/server/methods/op_find.js +23 -21
- package/dist/server/methods/op_find.js.map +1 -1
- package/dist/server/methods/op_hide.d.ts +3 -1
- package/dist/server/methods/op_hide.d.ts.map +1 -1
- package/dist/server/methods/op_hide.js +23 -21
- package/dist/server/methods/op_hide.js.map +1 -1
- package/dist/server/methods/op_move.d.ts +3 -1
- package/dist/server/methods/op_move.d.ts.map +1 -1
- package/dist/server/methods/op_move.js +23 -21
- package/dist/server/methods/op_move.js.map +1 -1
- package/dist/server/methods/op_parse.d.ts +3 -1
- package/dist/server/methods/op_parse.d.ts.map +1 -1
- package/dist/server/methods/op_parse.js +24 -22
- package/dist/server/methods/op_parse.js.map +1 -1
- package/dist/server/methods/op_read.d.ts +3 -1
- package/dist/server/methods/op_read.d.ts.map +1 -1
- package/dist/server/methods/op_read.js +23 -21
- package/dist/server/methods/op_read.js.map +1 -1
- package/dist/server/methods/op_send.d.ts +3 -1
- package/dist/server/methods/op_send.d.ts.map +1 -1
- package/dist/server/methods/op_send.js +22 -20
- package/dist/server/methods/op_send.js.map +1 -1
- package/dist/server/methods/op_show.d.ts +3 -1
- package/dist/server/methods/op_show.d.ts.map +1 -1
- package/dist/server/methods/op_show.js +23 -21
- package/dist/server/methods/op_show.js.map +1 -1
- package/dist/server/methods/ping.d.ts +3 -1
- package/dist/server/methods/ping.d.ts.map +1 -1
- package/dist/server/methods/ping.js +8 -6
- package/dist/server/methods/ping.js.map +1 -1
- package/dist/server/methods/providers_list.d.ts +3 -1
- package/dist/server/methods/providers_list.d.ts.map +1 -1
- package/dist/server/methods/providers_list.js +19 -17
- package/dist/server/methods/providers_list.js.map +1 -1
- package/dist/server/methods/session_attach.d.ts +3 -1
- package/dist/server/methods/session_attach.d.ts.map +1 -1
- package/dist/server/methods/session_attach.js +43 -41
- package/dist/server/methods/session_attach.js.map +1 -1
- package/dist/server/methods/session_create.d.ts +3 -1
- package/dist/server/methods/session_create.d.ts.map +1 -1
- package/dist/server/methods/session_create.js +51 -49
- package/dist/server/methods/session_create.js.map +1 -1
- package/dist/server/methods/session_list.d.ts +3 -1
- package/dist/server/methods/session_list.d.ts.map +1 -1
- package/dist/server/methods/session_list.js +9 -7
- package/dist/server/methods/session_list.js.map +1 -1
- package/dist/server/methods/session_runs.d.ts +3 -1
- package/dist/server/methods/session_runs.d.ts.map +1 -1
- package/dist/server/methods/session_runs.js +19 -17
- package/dist/server/methods/session_runs.js.map +1 -1
- package/dist/server/methods/session_set_persona.d.ts +3 -1
- package/dist/server/methods/session_set_persona.d.ts.map +1 -1
- package/dist/server/methods/session_set_persona.js +28 -26
- package/dist/server/methods/session_set_persona.js.map +1 -1
- package/dist/server/methods/session_set_root.d.ts +3 -1
- package/dist/server/methods/session_set_root.d.ts.map +1 -1
- package/dist/server/methods/session_set_root.js +31 -29
- package/dist/server/methods/session_set_root.js.map +1 -1
- package/dist/server/noProposals.d.ts +6 -0
- package/dist/server/noProposals.d.ts.map +1 -0
- package/dist/server/noProposals.js +37 -0
- package/dist/server/noProposals.js.map +1 -0
- package/dist/server/yolo.d.ts +3 -1
- package/dist/server/yolo.d.ts.map +1 -1
- package/dist/server/yolo.js +15 -13
- package/dist/server/yolo.js.map +1 -1
- package/package.json +71 -32
- package/bin/plurnk-service.js +0 -112
- /package/migrations/{001_schema.sql → 0000-00-00.01_schema.sql} +0 -0
package/dist/server/Daemon.js
CHANGED
|
@@ -4,41 +4,43 @@
|
|
|
4
4
|
import { WebSocketServer } from "ws";
|
|
5
5
|
import { readFile } from "node:fs/promises";
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
|
-
import {
|
|
7
|
+
import { Paths } from "../index.js";
|
|
8
8
|
import Engine from "../core/Engine.js";
|
|
9
|
+
import ExecutorRegistry from "../core/ExecutorRegistry.js";
|
|
9
10
|
import SchemeRegistry from "../core/SchemeRegistry.js";
|
|
10
11
|
import { Mimetypes } from "@plurnk/plurnk-mimetypes";
|
|
11
|
-
import
|
|
12
|
+
import PluginLoader from "../core/PluginLoader.js";
|
|
12
13
|
import MethodRegistry from "./MethodRegistry.js";
|
|
13
14
|
import ClientConnection from "./ClientConnection.js";
|
|
14
|
-
import
|
|
15
|
+
import LogEntry from "./logEntry.js";
|
|
16
|
+
import Yolo from "./yolo.js";
|
|
17
|
+
import NoProposals from "./noProposals.js";
|
|
15
18
|
import { DEFAULT_LOOP_FLAGS } from "../core/scheme-types.js";
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
import
|
|
26
|
-
import
|
|
27
|
-
import
|
|
28
|
-
import
|
|
29
|
-
import
|
|
30
|
-
import
|
|
31
|
-
import
|
|
32
|
-
import
|
|
33
|
-
import
|
|
34
|
-
import
|
|
35
|
-
import
|
|
36
|
-
import
|
|
37
|
-
import
|
|
38
|
-
import
|
|
39
|
-
import
|
|
40
|
-
import
|
|
41
|
-
import { attachYolo } from "./yolo.js";
|
|
19
|
+
import PingMethod from "./methods/ping.js";
|
|
20
|
+
import DiscoverMethod from "./methods/discover.js";
|
|
21
|
+
import SessionCreateMethod from "./methods/session_create.js";
|
|
22
|
+
import SessionListMethod from "./methods/session_list.js";
|
|
23
|
+
import SessionAttachMethod from "./methods/session_attach.js";
|
|
24
|
+
import SessionRunsMethod from "./methods/session_runs.js";
|
|
25
|
+
import SessionSetRootMethod from "./methods/session_set_root.js";
|
|
26
|
+
import SessionSetPersonaMethod from "./methods/session_set_persona.js";
|
|
27
|
+
import OpEditMethod from "./methods/op_edit.js";
|
|
28
|
+
import OpReadMethod from "./methods/op_read.js";
|
|
29
|
+
import OpFindMethod from "./methods/op_find.js";
|
|
30
|
+
import OpShowMethod from "./methods/op_show.js";
|
|
31
|
+
import OpHideMethod from "./methods/op_hide.js";
|
|
32
|
+
import OpCopyMethod from "./methods/op_copy.js";
|
|
33
|
+
import OpMoveMethod from "./methods/op_move.js";
|
|
34
|
+
import OpSendMethod from "./methods/op_send.js";
|
|
35
|
+
import OpExecMethod from "./methods/op_exec.js";
|
|
36
|
+
import OpDispatchMethod from "./methods/op_dispatch.js";
|
|
37
|
+
import OpParseMethod from "./methods/op_parse.js";
|
|
38
|
+
import LoopRunMethod from "./methods/loop_run.js";
|
|
39
|
+
import LoopCancelMethod from "./methods/loop_cancel.js";
|
|
40
|
+
import EntryReadMethod from "./methods/entry_read.js";
|
|
41
|
+
import LogReadMethod from "./methods/log_read.js";
|
|
42
|
+
import ProvidersListMethod from "./methods/providers_list.js";
|
|
43
|
+
import LoopResolveMethod from "./methods/loop_resolve.js";
|
|
42
44
|
export default class Daemon {
|
|
43
45
|
#db;
|
|
44
46
|
#engine;
|
|
@@ -49,14 +51,21 @@ export default class Daemon {
|
|
|
49
51
|
#nodeModulesPath;
|
|
50
52
|
#wss = null;
|
|
51
53
|
#connections = new Set();
|
|
52
|
-
// Run-level drain registry
|
|
53
|
-
// drain
|
|
54
|
-
// drain
|
|
54
|
+
// Run-level drain registry. At most one drain per run. The stored object
|
|
55
|
+
// is the drain's identity handle: start/exit compare it by reference so a
|
|
56
|
+
// drain exiting never clobbers a successor that raced in, and a loop
|
|
57
|
+
// enqueued during teardown is never stranded. A drain is a pure queue
|
|
58
|
+
// consumer (claim → run → exit on empty queue); streams live independently
|
|
59
|
+
// (subscriptions + Exec.idle), and a concluding stream routes through
|
|
60
|
+
// inject() like any other loop source.
|
|
55
61
|
#activeDrains = new Map();
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
|
|
62
|
+
// Per-run cancellation scope. Loops AND the streams they spawn (execs)
|
|
63
|
+
// share this signal, so loop.cancel / shutdown abort it once and every
|
|
64
|
+
// in-flight subscription tears down — even a spawn that registers AFTER the
|
|
65
|
+
// cancel self-aborts against the already-aborted signal (no race). Outlives
|
|
66
|
+
// any single (ephemeral) drain; replaced with a fresh controller once
|
|
67
|
+
// aborted so a later loop.run isn't born cancelled.
|
|
68
|
+
#runAborts = new Map();
|
|
60
69
|
constructor({ db, schemes, mimetypes, provider, nodeModulesPath, }) {
|
|
61
70
|
this.#db = db;
|
|
62
71
|
this.#schemes = schemes ?? new SchemeRegistry();
|
|
@@ -74,6 +83,11 @@ export default class Daemon {
|
|
|
74
83
|
});
|
|
75
84
|
this.#engine = new Engine({
|
|
76
85
|
db, schemes: this.#schemes, mimetypes: this.#mimetypes,
|
|
86
|
+
// Same provider-backed source as the Mimetypes tokenize lambda
|
|
87
|
+
// above; sync here because countTokens is sync (§2.1) and the
|
|
88
|
+
// write helpers store the count inline. Divisor tripwire only
|
|
89
|
+
// until a provider is resolved.
|
|
90
|
+
tokenize: (text) => this.#provider?.countTokens(text) ?? Math.ceil(text.length / 4),
|
|
77
91
|
streamEventNotify: (sessionId, event) => this.notifyStreamEvent(sessionId, event),
|
|
78
92
|
wakeRunNotify: (payload) => { void this.#handleWakeRun(payload); },
|
|
79
93
|
telemetryEventNotify: (sessionId, payload) => this.notifyTelemetryEvent(sessionId, payload),
|
|
@@ -102,7 +116,11 @@ export default class Daemon {
|
|
|
102
116
|
});
|
|
103
117
|
// In-tree YOLO listener — auto-accepts proposals when the loop's
|
|
104
118
|
// persisted flags.yolo === true. Skips client roundtrip entirely.
|
|
105
|
-
attachYolo(this.#engine, this.#db);
|
|
119
|
+
Yolo.attachYolo(this.#engine, this.#db);
|
|
120
|
+
// Inverse of YOLO: auto-REJECT proposals in-process when the loop's
|
|
121
|
+
// persisted flags.noProposals === true (client has no review channel).
|
|
122
|
+
// The model sees an ordinary 400, never the orchestration reason.
|
|
123
|
+
NoProposals.attachNoProposals(this.#engine, this.#db);
|
|
106
124
|
}
|
|
107
125
|
get registry() { return this.#registry; }
|
|
108
126
|
get engine() { return this.#engine; }
|
|
@@ -116,6 +134,11 @@ export default class Daemon {
|
|
|
116
134
|
// Mimetypes owns its own discovery scan over @plurnk/plurnk-mimetypes-*
|
|
117
135
|
// packages; pre-warm it so first index render doesn't pay the cost.
|
|
118
136
|
await this.#mimetypes.ready();
|
|
137
|
+
// Discover + probe the installed executor siblings, then hand the
|
|
138
|
+
// registry to the engine for exec dispatch (plurnk-service#181). The
|
|
139
|
+
// shell is the default runtime, so its executor must boot usable.
|
|
140
|
+
const executors = await ExecutorRegistry.build({ defaultRuntime: "sh" });
|
|
141
|
+
this.#engine.setExecutors(executors);
|
|
119
142
|
return new Promise((resolve, reject) => {
|
|
120
143
|
const wss = new WebSocketServer({ host, port });
|
|
121
144
|
wss.on("listening", () => {
|
|
@@ -154,8 +177,14 @@ export default class Daemon {
|
|
|
154
177
|
// to completion, (3) drain streaming schemes' background work
|
|
155
178
|
// (exec spawn cleanup, channel writes). Only THEN close the DB
|
|
156
179
|
// upstream — drain queries hit the DB right up until they exit.
|
|
157
|
-
|
|
158
|
-
|
|
180
|
+
// Abort every run's cancellation scope — stops in-flight loops AND the
|
|
181
|
+
// streams (background execs) linked to them, so idle() doesn't block on
|
|
182
|
+
// a long-running command. Covers runs whose drain already exited but
|
|
183
|
+
// whose exec is still in flight.
|
|
184
|
+
for (const scope of this.#runAborts.values()) {
|
|
185
|
+
if (!scope.signal.aborted)
|
|
186
|
+
scope.abort("daemon_stopping");
|
|
187
|
+
}
|
|
159
188
|
const drainPromises = [...this.#activeDrains.values()].map((d) => d.promise);
|
|
160
189
|
await Promise.allSettled(drainPromises);
|
|
161
190
|
await this.#drainStreamingSchemes();
|
|
@@ -168,31 +197,31 @@ export default class Daemon {
|
|
|
168
197
|
await exec.idle();
|
|
169
198
|
}
|
|
170
199
|
#registerBuiltins() {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
200
|
+
PingMethod.register(this.#registry);
|
|
201
|
+
DiscoverMethod.register(this.#registry);
|
|
202
|
+
SessionCreateMethod.register(this.#registry);
|
|
203
|
+
SessionListMethod.register(this.#registry);
|
|
204
|
+
SessionAttachMethod.register(this.#registry);
|
|
205
|
+
SessionRunsMethod.register(this.#registry);
|
|
206
|
+
SessionSetRootMethod.register(this.#registry);
|
|
207
|
+
SessionSetPersonaMethod.register(this.#registry);
|
|
208
|
+
OpEditMethod.register(this.#registry);
|
|
209
|
+
OpReadMethod.register(this.#registry);
|
|
210
|
+
OpFindMethod.register(this.#registry);
|
|
211
|
+
OpShowMethod.register(this.#registry);
|
|
212
|
+
OpHideMethod.register(this.#registry);
|
|
213
|
+
OpCopyMethod.register(this.#registry);
|
|
214
|
+
OpMoveMethod.register(this.#registry);
|
|
215
|
+
OpSendMethod.register(this.#registry);
|
|
216
|
+
OpExecMethod.register(this.#registry);
|
|
217
|
+
OpDispatchMethod.register(this.#registry);
|
|
218
|
+
OpParseMethod.register(this.#registry);
|
|
219
|
+
LoopRunMethod.register(this.#registry);
|
|
220
|
+
LoopCancelMethod.register(this.#registry);
|
|
221
|
+
LoopResolveMethod.register(this.#registry);
|
|
222
|
+
EntryReadMethod.register(this.#registry);
|
|
223
|
+
LogReadMethod.register(this.#registry);
|
|
224
|
+
ProvidersListMethod.register(this.#registry);
|
|
196
225
|
}
|
|
197
226
|
#registerNotifications() {
|
|
198
227
|
this.#registry.registerNotification("log/entry", {
|
|
@@ -224,6 +253,7 @@ export default class Daemon {
|
|
|
224
253
|
description: "A channel's content grew or its state transitioned. Scoped to the entry's session. Metadata-only; clients fetch new content via entry.read or op.read.",
|
|
225
254
|
params: {
|
|
226
255
|
entryId: "number — the entry whose channel changed",
|
|
256
|
+
target: "string — the entry's URI (scheme://pathname); clients route on this without an entryId→URI lookup",
|
|
227
257
|
channel: "string — the channel name",
|
|
228
258
|
state: "string — current state (static, active, closed, errored)",
|
|
229
259
|
contentLength: "number — current length of the channel's content",
|
|
@@ -240,6 +270,7 @@ export default class Daemon {
|
|
|
240
270
|
description: "A streaming-scheme subscription closed (the underlying connection / subprocess finished, errored, or was cancelled). Scoped to the entry's session. wakeAction describes whether the daemon opened a fresh loop to surface the conclusion to the model.",
|
|
241
271
|
params: {
|
|
242
272
|
entryId: "number",
|
|
273
|
+
target: "string — the entry's URI (scheme://pathname)",
|
|
243
274
|
subscriptionId: "number",
|
|
244
275
|
scheme: "string — the scheme that owned the subscription (e.g. 'exec')",
|
|
245
276
|
closeStatus: "number — 200 (clean) / 500 (error) / 499 (aborted)",
|
|
@@ -281,21 +312,14 @@ export default class Daemon {
|
|
|
281
312
|
*/
|
|
282
313
|
async inject(args) {
|
|
283
314
|
const { sessionId, runId, prompt } = args;
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
this.#pokeDrain(runId);
|
|
289
|
-
const drainActive = this.#activeDrains.has(runId);
|
|
290
|
-
if (drainActive) {
|
|
315
|
+
// Active loop (status=102)? Fold the wake/prompt into its next turn.
|
|
316
|
+
// engine.inject returns null when no loop is currently executing, so
|
|
317
|
+
// we enqueue a fresh loop below and ensure a drain claims it.
|
|
318
|
+
if (this.#activeDrains.has(runId)) {
|
|
291
319
|
const result = await this.#engine.inject(runId, prompt);
|
|
292
320
|
if (result !== null) {
|
|
293
321
|
return { action: "injected_next_turn", loopId: result.loopId, turnSeq: result.turnSeq };
|
|
294
322
|
}
|
|
295
|
-
// Race: #activeDrains exists but drain hasn't yet claimed a
|
|
296
|
-
// loop (status=102). Fall through to the enqueue path — but
|
|
297
|
-
// we must NOT start a parallel drain. The existing drain
|
|
298
|
-
// will claim this loop on a subsequent iteration.
|
|
299
323
|
}
|
|
300
324
|
// Enqueue a fresh loop. Persist flags + persona override on the row.
|
|
301
325
|
const seqRow = await this.#db.loop_run_next_sequence.get({ run_id: runId });
|
|
@@ -313,18 +337,19 @@ export default class Daemon {
|
|
|
313
337
|
loop_id: loopId, flags: JSON.stringify(merged),
|
|
314
338
|
});
|
|
315
339
|
}
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
340
|
+
// Guarantee a drain claims the loop we just enqueued. Synchronous
|
|
341
|
+
// check-and-start (no await between the membership test and the
|
|
342
|
+
// registry write): a live drain re-claims it; otherwise we start one.
|
|
343
|
+
// The drain's exit coordinates via an identity-checked re-claim so the
|
|
344
|
+
// loop is never stranded (the lost-loop hang). firstLoopPromise is
|
|
345
|
+
// present only when THIS call started the drain — loop.run keys its
|
|
346
|
+
// fast-path response on that.
|
|
347
|
+
const started = this.#ensureDrain({
|
|
323
348
|
sessionId, runId, provider: args.provider,
|
|
324
349
|
systemPrompt: args.systemPrompt, personaDefault: args.persona,
|
|
325
350
|
maxTurns: args.maxTurns ?? Number(process.env.PLURNK_MAX_TURNS ?? "50"),
|
|
326
351
|
});
|
|
327
|
-
return { action: "enqueued_new_loop", loopId,
|
|
352
|
+
return { action: "enqueued_new_loop", loopId, ...(started ?? {}) };
|
|
328
353
|
}
|
|
329
354
|
/**
|
|
330
355
|
* Start a drain for the given run. The drain claims queued loops via
|
|
@@ -341,7 +366,12 @@ export default class Daemon {
|
|
|
341
366
|
*/
|
|
342
367
|
#startDrain(opts) {
|
|
343
368
|
const { sessionId, runId, provider, systemPrompt, personaDefault, maxTurns } = opts;
|
|
344
|
-
|
|
369
|
+
// The drain runs under the run's cancellation scope (shared with the
|
|
370
|
+
// execs its loops spawn), so loop.cancel/shutdown abort it as a unit.
|
|
371
|
+
const controller = this.#runSignal(runId);
|
|
372
|
+
const handle = {
|
|
373
|
+
controller, promise: Promise.resolve(),
|
|
374
|
+
};
|
|
345
375
|
let resolveFirst = () => { };
|
|
346
376
|
let rejectFirst = () => { };
|
|
347
377
|
const firstLoopPromise = new Promise((res, rej) => {
|
|
@@ -349,55 +379,65 @@ export default class Daemon {
|
|
|
349
379
|
rejectFirst = rej;
|
|
350
380
|
});
|
|
351
381
|
let firstSettled = false;
|
|
382
|
+
const claim = () => this.#db.drain_claim_next_loop.get({ run_id: runId });
|
|
352
383
|
const drainPromise = (async () => {
|
|
353
384
|
let loopsDrained = 0;
|
|
354
385
|
let lastResult = null;
|
|
355
386
|
try {
|
|
356
387
|
while (true) {
|
|
357
388
|
controller.signal.throwIfAborted();
|
|
358
|
-
|
|
359
|
-
if (loopRow
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
],
|
|
372
|
-
persona: personaDefault,
|
|
373
|
-
origin: "model",
|
|
374
|
-
onDispatch,
|
|
375
|
-
signal: controller.signal,
|
|
376
|
-
});
|
|
377
|
-
this.#broadcast({ sessionId }, null, "loop/terminated", {
|
|
378
|
-
loopId: loopRow.id,
|
|
379
|
-
finalStatus: result.finalStatus,
|
|
380
|
-
hitMaxTurns: result.hitMaxTurns,
|
|
381
|
-
});
|
|
382
|
-
loopsDrained++;
|
|
383
|
-
const loopResult = {
|
|
384
|
-
loopId: loopRow.id,
|
|
385
|
-
turnIds: result.turnIds,
|
|
386
|
-
finalStatus: result.finalStatus,
|
|
387
|
-
hitMaxTurns: result.hitMaxTurns,
|
|
388
|
-
};
|
|
389
|
-
lastResult = loopResult;
|
|
390
|
-
if (!firstSettled) {
|
|
391
|
-
firstSettled = true;
|
|
392
|
-
resolveFirst(loopResult);
|
|
393
|
-
}
|
|
394
|
-
continue;
|
|
389
|
+
let loopRow = await claim();
|
|
390
|
+
if (loopRow === undefined) {
|
|
391
|
+
// Queue empty → exit. Coordinate with a concurrent
|
|
392
|
+
// inject + #ensureDrain: relinquish ownership, then
|
|
393
|
+
// re-claim once. A loop that raced in during teardown
|
|
394
|
+
// is caught here (re-acquire + run it); otherwise exit.
|
|
395
|
+
// Identity-checked so we never delete a successor entry.
|
|
396
|
+
if (this.#activeDrains.get(runId) === handle)
|
|
397
|
+
this.#activeDrains.delete(runId);
|
|
398
|
+
loopRow = await claim();
|
|
399
|
+
if (loopRow === undefined)
|
|
400
|
+
break;
|
|
401
|
+
this.#activeDrains.set(runId, handle);
|
|
395
402
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
403
|
+
const onDispatch = (logEntryId) => {
|
|
404
|
+
void (async () => {
|
|
405
|
+
const entry = await LogEntry.fetchLogEntry(this.#db, logEntryId);
|
|
406
|
+
this.#broadcast({ sessionId }, null, "log/entry", { entry });
|
|
407
|
+
})();
|
|
408
|
+
};
|
|
409
|
+
const result = await this.#engine.runLoop({
|
|
410
|
+
provider, sessionId, runId, loopId: loopRow.id, maxTurns,
|
|
411
|
+
messages: [
|
|
412
|
+
{ role: "system", content: systemPrompt },
|
|
413
|
+
{ role: "user", content: loopRow.prompt },
|
|
414
|
+
],
|
|
415
|
+
persona: personaDefault,
|
|
416
|
+
origin: "model",
|
|
417
|
+
onDispatch,
|
|
418
|
+
signal: controller.signal,
|
|
419
|
+
});
|
|
420
|
+
this.#broadcast({ sessionId }, null, "loop/terminated", {
|
|
421
|
+
loopId: loopRow.id,
|
|
422
|
+
finalStatus: result.finalStatus,
|
|
423
|
+
hitMaxTurns: result.hitMaxTurns,
|
|
424
|
+
});
|
|
425
|
+
loopsDrained++;
|
|
426
|
+
const loopResult = {
|
|
427
|
+
loopId: loopRow.id,
|
|
428
|
+
turnIds: result.turnIds,
|
|
429
|
+
finalStatus: result.finalStatus,
|
|
430
|
+
hitMaxTurns: result.hitMaxTurns,
|
|
431
|
+
};
|
|
432
|
+
lastResult = loopResult;
|
|
433
|
+
if (!firstSettled) {
|
|
434
|
+
firstSettled = true;
|
|
435
|
+
resolveFirst(loopResult);
|
|
436
|
+
}
|
|
437
|
+
// A next-turn prompt this loop ended before consuming (a
|
|
438
|
+
// wake conclusion or a loop.run-while-active) is promoted to
|
|
439
|
+
// a fresh queued loop so it's never silently dropped.
|
|
440
|
+
await this.#reconcileOrphanedWake(runId, loopRow.id);
|
|
401
441
|
}
|
|
402
442
|
}
|
|
403
443
|
catch (err) {
|
|
@@ -412,59 +452,83 @@ export default class Daemon {
|
|
|
412
452
|
firstSettled = true;
|
|
413
453
|
rejectFirst(new Error("drain exited without producing a result"));
|
|
414
454
|
}
|
|
415
|
-
this.#activeDrains.
|
|
416
|
-
|
|
455
|
+
if (this.#activeDrains.get(runId) === handle)
|
|
456
|
+
this.#activeDrains.delete(runId);
|
|
417
457
|
}
|
|
418
458
|
return { loopsDrained, lastResult };
|
|
419
459
|
})();
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
// if relevant, or was already logged inside the drain body.
|
|
460
|
+
handle.promise = drainPromise;
|
|
461
|
+
this.#activeDrains.set(runId, handle);
|
|
462
|
+
// Swallow unhandled rejections (drain aborts with no awaiter); the
|
|
463
|
+
// error already surfaced via firstLoopPromise or was logged inside.
|
|
425
464
|
drainPromise.catch(() => { });
|
|
426
|
-
// firstLoopPromise also needs a handler in case nobody awaits it
|
|
427
|
-
// (e.g. wake-on-completion enqueue path doesn't always read it).
|
|
428
465
|
firstLoopPromise.catch(() => { });
|
|
429
466
|
return { firstLoopPromise, drainPromise };
|
|
430
467
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
468
|
+
// Idempotent, synchronous drain guarantee. A live drain will claim the
|
|
469
|
+
// just-enqueued loop in its own iteration (or its exit re-claim) → return
|
|
470
|
+
// null. Otherwise start one. MUST be called synchronously after the
|
|
471
|
+
// enqueue (no await between) so the membership test and #startDrain's
|
|
472
|
+
// registry write are one tick — two concurrent injects can't both start a
|
|
473
|
+
// drain for the same run.
|
|
474
|
+
#ensureDrain(opts) {
|
|
475
|
+
if (this.#activeDrains.has(opts.runId))
|
|
476
|
+
return null;
|
|
477
|
+
return this.#startDrain(opts);
|
|
437
478
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
479
|
+
// After a loop terminates, promote any next-turn prompt it never consumed —
|
|
480
|
+
// an injected wake (stream conclusion) or a loop.run-while-active prompt
|
|
481
|
+
// that landed on a turn the loop didn't reach — into a fresh queued loop.
|
|
482
|
+
// The drain claims it on its next iteration, so a conclusion or client
|
|
483
|
+
// prompt is never silently dropped. Inherits the ended loop's flags + persona.
|
|
484
|
+
async #reconcileOrphanedWake(runId, endedLoopId) {
|
|
485
|
+
const prefix = `prompt/${endedLoopId}/`;
|
|
486
|
+
const orphan = await this.#db.drain_orphaned_prompt_for_loop.get({ loop_id: endedLoopId, pattern: `${prefix}%`, prefix_len: prefix.length });
|
|
487
|
+
if (orphan === undefined)
|
|
488
|
+
return;
|
|
489
|
+
const seqRow = await this.#db.loop_run_next_sequence.get({ run_id: runId });
|
|
490
|
+
if (seqRow === undefined)
|
|
491
|
+
throw new Error("reconcileOrphanedWake: next-sequence query returned no row");
|
|
492
|
+
const fresh = await this.#db.drain_enqueue_loop.get({
|
|
493
|
+
run_id: runId, sequence: seqRow.next, prompt: orphan.body, persona: orphan.persona,
|
|
453
494
|
});
|
|
495
|
+
if (fresh === undefined)
|
|
496
|
+
throw new Error("reconcileOrphanedWake: enqueue returned no row");
|
|
497
|
+
if (orphan.flags !== null) {
|
|
498
|
+
await this.#db.engine_set_loop_flags.run({ loop_id: fresh.id, flags: orphan.flags });
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// The run's cancellation scope — lazily created, and replaced once aborted
|
|
502
|
+
// so a later loop.run gets a live signal. The drain and the execs its loops
|
|
503
|
+
// spawn all run under it.
|
|
504
|
+
#runSignal(runId) {
|
|
505
|
+
const existing = this.#runAborts.get(runId);
|
|
506
|
+
if (existing !== undefined && !existing.signal.aborted)
|
|
507
|
+
return existing;
|
|
508
|
+
const fresh = new AbortController();
|
|
509
|
+
this.#runAborts.set(runId, fresh);
|
|
510
|
+
return fresh;
|
|
454
511
|
}
|
|
455
512
|
/**
|
|
456
|
-
* Cancel the
|
|
457
|
-
* the
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
513
|
+
* Cancel the run's in-flight work (loop.cancel). One abort, one scope: the
|
|
514
|
+
* run signal stops the running loop's turn generation AND tears down every
|
|
515
|
+
* stream linked to it — a background exec that outlived its loop, or even a
|
|
516
|
+
* spawn that registers after this abort (it self-aborts against the aborted
|
|
517
|
+
* signal). Returns cancelled iff there was work. Queued loops stay enqueued.
|
|
461
518
|
*/
|
|
462
519
|
cancelDrain(runId, reason = "user_cancelled") {
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
return
|
|
520
|
+
const hadWork = this.#activeDrains.has(runId) || this.#runHasActiveStreams(runId);
|
|
521
|
+
const scope = this.#runAborts.get(runId);
|
|
522
|
+
if (scope !== undefined && !scope.signal.aborted)
|
|
523
|
+
scope.abort(reason);
|
|
524
|
+
return hadWork;
|
|
525
|
+
}
|
|
526
|
+
// Does the run have an in-flight stream (a background exec)? Used only for
|
|
527
|
+
// loop.cancel's cancelled=true/false answer; the teardown itself rides the
|
|
528
|
+
// run signal. Duck-typed like #drainStreamingSchemes.
|
|
529
|
+
#runHasActiveStreams(runId) {
|
|
530
|
+
const exec = this.#schemes.get("exec");
|
|
531
|
+
return exec?.hasActiveSpawns?.(runId) ?? false;
|
|
468
532
|
}
|
|
469
533
|
/**
|
|
470
534
|
* Wake-on-completion handler. Streaming schemes call this when a
|
|
@@ -480,12 +544,6 @@ export default class Daemon {
|
|
|
480
544
|
* Rummy parallel: plugins/stream/stream.js stream/completed wake:true.
|
|
481
545
|
*/
|
|
482
546
|
async #handleWakeRun(payload) {
|
|
483
|
-
// Every subscription close pokes the run's drain (if any) so a
|
|
484
|
-
// parked stream-aware drain re-checks active subs count. This
|
|
485
|
-
// happens regardless of whether we go on to inject a new loop,
|
|
486
|
-
// so the 499-skipped / no-provider paths still wake the drain
|
|
487
|
-
// out of its "queue empty, waiting for streams" state.
|
|
488
|
-
this.#pokeDrain(payload.runId);
|
|
489
547
|
// Aborted streams don't wake — the abort was deliberate.
|
|
490
548
|
if (payload.closeStatus === 499) {
|
|
491
549
|
this.#broadcast({ sessionId: payload.sessionId }, null, "stream/concluded", {
|
|
@@ -500,8 +558,8 @@ export default class Daemon {
|
|
|
500
558
|
return;
|
|
501
559
|
}
|
|
502
560
|
try {
|
|
503
|
-
const systemPrompt = await readFile(
|
|
504
|
-
const personaText = await readFile(
|
|
561
|
+
const systemPrompt = await readFile(Paths.instructionsSystem, "utf8");
|
|
562
|
+
const personaText = await readFile(Paths.defaultPersona, "utf8");
|
|
505
563
|
// Unified path: this.inject decides whether to write a prompt
|
|
506
564
|
// entry for an active drain's next turn (no-op-active-loop) or
|
|
507
565
|
// enqueue a fresh loop + drain (opened-loop). The summary
|
|
@@ -558,11 +616,11 @@ export default class Daemon {
|
|
|
558
616
|
// bin script). Mimetypes self-discovers — Mimetypes.ready() in start()
|
|
559
617
|
// scans @plurnk/plurnk-mimetypes-* packages via the framework's own
|
|
560
618
|
// discover().
|
|
561
|
-
const plugins = await discoverPlugins(this.#nodeModulesPath);
|
|
619
|
+
const plugins = await PluginLoader.discoverPlugins(this.#nodeModulesPath);
|
|
562
620
|
for (const plugin of plugins) {
|
|
563
621
|
if (plugin.manifest.kind !== "scheme")
|
|
564
622
|
continue;
|
|
565
|
-
const instance = await loadPlugin(plugin);
|
|
623
|
+
const instance = await PluginLoader.loadPlugin(plugin);
|
|
566
624
|
this.#schemes.register(plugin.manifest.name, instance);
|
|
567
625
|
}
|
|
568
626
|
}
|