@plurnk/plurnk-agui 0.3.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.example +37 -0
- package/README.md +65 -0
- package/SPEC.md +115 -0
- package/dist/AguiPlus.d.ts +58 -0
- package/dist/AguiPlus.d.ts.map +1 -0
- package/dist/AguiPlus.js +67 -0
- package/dist/AguiPlus.js.map +1 -0
- package/dist/DaemonClient.d.ts +9 -0
- package/dist/DaemonClient.d.ts.map +1 -0
- package/dist/DaemonClient.js +55 -0
- package/dist/DaemonClient.js.map +1 -0
- package/dist/DaemonSeam.d.ts +103 -0
- package/dist/DaemonSeam.d.ts.map +1 -0
- package/dist/DaemonSeam.js +7 -0
- package/dist/DaemonSeam.js.map +1 -0
- package/dist/EventRouter.d.ts +14 -0
- package/dist/EventRouter.d.ts.map +1 -0
- package/dist/EventRouter.js +27 -0
- package/dist/EventRouter.js.map +1 -0
- package/dist/Module.d.ts +23 -0
- package/dist/Module.d.ts.map +1 -0
- package/dist/Module.js +200 -0
- package/dist/Module.js.map +1 -0
- package/dist/Portal.d.ts +33 -0
- package/dist/Portal.d.ts.map +1 -0
- package/dist/Portal.js +62 -0
- package/dist/Portal.js.map +1 -0
- package/dist/ProposalHitl.d.ts +14 -0
- package/dist/ProposalHitl.d.ts.map +1 -0
- package/dist/ProposalHitl.js +64 -0
- package/dist/ProposalHitl.js.map +1 -0
- package/dist/Server.d.ts +10 -0
- package/dist/Server.d.ts.map +1 -0
- package/dist/Server.js +232 -0
- package/dist/Server.js.map +1 -0
- package/dist/Translator.d.ts +17 -0
- package/dist/Translator.d.ts.map +1 -0
- package/dist/Translator.js +200 -0
- package/dist/Translator.js.map +1 -0
- package/dist/bin/serve.d.ts +3 -0
- package/dist/bin/serve.d.ts.map +1 -0
- package/dist/bin/serve.js +9 -0
- package/dist/bin/serve.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +134 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +65 -0
package/.env.example
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# @plurnk/plurnk-agui — every knob lives here; no code fallback hides one.
|
|
3
|
+
# ============================================================================
|
|
4
|
+
|
|
5
|
+
# The plurnk daemon this bridge fronts (the bridge is an ordinary client of the
|
|
6
|
+
# JSON-RPC-over-WebSocket wire — it holds no state the daemon doesn't).
|
|
7
|
+
PLURNK_AGUI_DAEMON_URL=ws://127.0.0.1:3044
|
|
8
|
+
|
|
9
|
+
# The AG-UI listener. POST / with Accept: text/event-stream is the run endpoint
|
|
10
|
+
# (the AG-UI HttpAgent default shape); POST /resolve answers proposals/questions.
|
|
11
|
+
PLURNK_AGUI_HOST=127.0.0.1
|
|
12
|
+
PLURNK_AGUI_PORT=3080
|
|
13
|
+
|
|
14
|
+
# Session naming: an AG-UI threadId maps to the daemon session `<prefix>-<threadId>`
|
|
15
|
+
# (created on the thread's first run, reattached on every run after — AG-UI threads
|
|
16
|
+
# ARE plurnk sessions, so the extended context persists across runs).
|
|
17
|
+
PLURNK_AGUI_SESSION_PREFIX=agui
|
|
18
|
+
|
|
19
|
+
# Operator questions ([300]): 1 = sessions created by this bridge request
|
|
20
|
+
# settings.questions, so the model's asks surface as plurnk.proposal events for
|
|
21
|
+
# the frontend to answer via POST /resolve (the daemon's PLURNK_QUESTIONS=0
|
|
22
|
+
# ceiling still wins). 0 = headless semantics — asks are refused with the
|
|
23
|
+
# self-decide steer, exactly like bench.
|
|
24
|
+
PLURNK_AGUI_QUESTIONS=1
|
|
25
|
+
|
|
26
|
+
# Loop flags forwarded on every run this bridge starts. yolo=1 auto-accepts
|
|
27
|
+
# file-edit proposals daemon-side (questions still stop the world — the daemon
|
|
28
|
+
# never yolo-answers a human question).
|
|
29
|
+
PLURNK_AGUI_YOLO=0
|
|
30
|
+
|
|
31
|
+
# Per-run turn ceiling forwarded to loop.run.
|
|
32
|
+
PLURNK_AGUI_MAX_TURNS=24
|
|
33
|
+
|
|
34
|
+
# Bridge auth — EMPTY (shipped) = local trust (bind 127.0.0.1 and the port IS the boundary,
|
|
35
|
+
# the same posture as the daemon's own WS). Set a token and every POST must carry
|
|
36
|
+
# `Authorization: Bearer <token>` — the knob for any non-loopback deployment.
|
|
37
|
+
PLURNK_AGUI_TOKEN=
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @plurnk/plurnk-agui
|
|
2
|
+
|
|
3
|
+
The plurnk daemon's **transport daughter**: owns the client interface, serving
|
|
4
|
+
**AG-UI+** — the [Agent-User Interaction Protocol](https://docs.ag-ui.com) plus plurnk
|
|
5
|
+
metadata extensions — as the **sole** client interaction surface. Any AG-UI frontend
|
|
6
|
+
(CopilotKit, community UIs, the plurnk CLI/TUI/nvim clients) is a plurnk client.
|
|
7
|
+
Vendor-agnostic, MIT.
|
|
8
|
+
|
|
9
|
+
## Topology
|
|
10
|
+
|
|
11
|
+
An **in-process module** of the daemon, not an external service: the daemon activates it
|
|
12
|
+
at boot (`daemon.registerModule(Module.init(opts))`), handing it the curated core seam
|
|
13
|
+
(event source, loop-control, op dispatch, journal/metadata reads, proposal HITL,
|
|
14
|
+
session lifecycle — plurnk-service#355). The module opens its own HTTP/SSE listener and
|
|
15
|
+
owns the transport+security line at that edge: bearer auth, per-session authorization,
|
|
16
|
+
input validation. Below the seam: plurnk terms (loop/turn/proposal). Above: AG-UI terms
|
|
17
|
+
(run/message/tool-call/state). This module is the translation.
|
|
18
|
+
|
|
19
|
+
A legacy standalone bridge (`plurnk-agui` bin: WS client → SSE server) ships during the
|
|
20
|
+
migration and retires at cutover.
|
|
21
|
+
|
|
22
|
+
## The interface (AG-UI+)
|
|
23
|
+
|
|
24
|
+
One endpoint: `POST /` with `RunAgentInput` (`threadId`, `runId`, `messages`,
|
|
25
|
+
`forwardedProps`) → `text/event-stream` of AG-UI events.
|
|
26
|
+
|
|
27
|
+
- **Runs**: turns are `STEP_*`; PLAN is `THINKING_TEXT_MESSAGE_*`; SEND bodies are
|
|
28
|
+
`TEXT_MESSAGE_*`; every other op row is a `TOOL_CALL_*` triple + `TOOL_CALL_RESULT`.
|
|
29
|
+
Numbers pass through verbatim (`contextSize` = the daemon's effective prompt budget).
|
|
30
|
+
- **HITL, terminate-resume**: a stopped-world proposal (file edit, exec, MCP auth) emits
|
|
31
|
+
a `request_approval` tool-call — a `SEND[300]` operator question emits
|
|
32
|
+
`request_user_input` — and the run **finishes** while the loop stays paused in-engine.
|
|
33
|
+
The next run's tool-result message (`toolCallId: "prop:<id>"`, content
|
|
34
|
+
`{decision: accept|reject|cancel, body?}`) resolves it; the continued loop streams there.
|
|
35
|
+
Pending proposals re-surface on (re)connect — a days-old question is discoverable.
|
|
36
|
+
- **Reads ride STATE**: providers/aliases/budget/session arrive as `STATE_SNAPSHOT` on
|
|
37
|
+
`RUN_STARTED` and `STATE_DELTA` on change — observed, not polled.
|
|
38
|
+
- **Session options** ride the thread's first run: `forwardedProps.plurnk`
|
|
39
|
+
(`projectRoot`, `constraints`, `settings`); per-run knobs (`maxTurns`, `flags`,
|
|
40
|
+
`alias`/`model`) every run.
|
|
41
|
+
- **Cancel**: dropping the SSE aborts a live loop (hangup is the abort); a
|
|
42
|
+
proposal-terminated run leaves the paused loop for the resume.
|
|
43
|
+
- An AG-UI `threadId` **is** a plurnk session (`<prefix>-<threadId>`) — extended context
|
|
44
|
+
persists across runs; history replays as `MESSAGES_SNAPSHOT` on reattach.
|
|
45
|
+
- **Tier 2 metadata** (`CUSTOM plurnk.*`: row, stream, telemetry, terminated) carries
|
|
46
|
+
what AG-UI has no term for — fold state, coordinates, tags, token truth. Generic
|
|
47
|
+
frontends skip it; family clients render it richly. The full contract: `SPEC.md`
|
|
48
|
+
(every `{§}` anchor cited by a test).
|
|
49
|
+
|
|
50
|
+
## Consume
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { Module } from "@plurnk/plurnk-agui";
|
|
54
|
+
daemon.registerModule(Module.init({ host: "127.0.0.1", port: 3080, sessionPrefix: "agui" }));
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Legacy bridge: `npx plurnk-agui` fronts `ws://127.0.0.1:3044`, listens on `:3080`;
|
|
58
|
+
config is `.env` over the shipped `.env.example` (every knob documented there).
|
|
59
|
+
|
|
60
|
+
## Dependencies
|
|
61
|
+
|
|
62
|
+
**Zero at runtime — no Zod, no SDK.** The AG-UI event shapes are hand-defined plain TS
|
|
63
|
+
(the official `@ag-ui/core` is 0.0.x and would drag Zod in, so it is deliberately NOT a
|
|
64
|
+
dependency). If the official SDK ever stabilizes dependency-free, adopting it is a
|
|
65
|
+
types-only swap.
|
package/SPEC.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# @plurnk/plurnk-agui — the projection contract
|
|
2
|
+
|
|
3
|
+
The bridge is **a client of the plurnk daemon and a server of AG-UI**: it consumes the same
|
|
4
|
+
JSON-RPC-over-WebSocket wire every plurnk client speaks, and emits the Agent-User Interaction
|
|
5
|
+
Protocol's SSE event stream. Zero daemon changes; the daemon's numbers and semantics pass
|
|
6
|
+
through, never recomputed. Every `{§}` anchor below is cited by a `[§]` test.
|
|
7
|
+
|
|
8
|
+
## Architecture
|
|
9
|
+
|
|
10
|
+
- **The bridge is an ordinary daemon client** {§agui-daemon-client} — it connects to
|
|
11
|
+
`PLURNK_AGUI_DAEMON_URL`, calls the public methods (`session.create`/`session.attach`,
|
|
12
|
+
`loop.run`, `loop.resolve`), and subscribes to the public notifications (`log/entry`,
|
|
13
|
+
`loop/proposal`, `loop/terminated`). It holds no state the daemon doesn't: a bridge restart
|
|
14
|
+
loses nothing (threads reattach by session name).
|
|
15
|
+
- **An AG-UI thread IS a plurnk session** {§agui-thread-is-session} — `threadId` maps to the
|
|
16
|
+
session `<PLURNK_AGUI_SESSION_PREFIX>-<threadId>`, created on the thread's first run with
|
|
17
|
+
`settings.questions` per `PLURNK_AGUI_QUESTIONS`, reattached on every run after. Plurnk's
|
|
18
|
+
extended context persists across AG-UI runs because the session does.
|
|
19
|
+
- **Zero runtime dependencies** {§agui-zero-dep} — the AG-UI event shapes are hand-defined
|
|
20
|
+
plain JSON (`src/types.ts`); the SSE encoding is `data: <json>\n\n`. The protocol is young;
|
|
21
|
+
a zero-dep daughter beats tracking SDK churn, and adopting the official SDK later is a
|
|
22
|
+
types-only swap.
|
|
23
|
+
|
|
24
|
+
## The projection {§agui-projection}
|
|
25
|
+
|
|
26
|
+
One daemon notification in, zero-or-more AG-UI events out (`Translator`, pure):
|
|
27
|
+
|
|
28
|
+
| plurnk wire | AG-UI events |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `loop.run` accepted | `RUN_STARTED` |
|
|
31
|
+
| `log/entry` turn boundary | `STEP_FINISHED` + `STEP_STARTED` (`turn-<id>`) |
|
|
32
|
+
| `log/entry` op=PLAN (model) | `THINKING_TEXT_MESSAGE_START/CONTENT/END` |
|
|
33
|
+
| `log/entry` op=SEND (model) | `TEXT_MESSAGE_START/CONTENT/END` + `CUSTOM plurnk.send` (signal/status) |
|
|
34
|
+
| `log/entry` other op (model) | `TOOL_CALL_START/ARGS/END` (+ `TOOL_CALL_RESULT` when rx exists) |
|
|
35
|
+
| `log/entry` op=model (mirror) | nothing — forensic, not speech |
|
|
36
|
+
| `log/entry` origin≠model | `CUSTOM plurnk.ambient` (foists, deltas, narrations) |
|
|
37
|
+
| `loop/proposal` | `CUSTOM plurnk.proposal` |
|
|
38
|
+
| `loop/terminated` | `STATE_DELTA` (budget) + `CUSTOM plurnk.terminated` (sessionId, loopId, turnIds, costPico, meta) + `RUN_FINISHED` (200) or `RUN_ERROR` (else) |
|
|
39
|
+
| `telemetry/event` | `CUSTOM plurnk.telemetry` |
|
|
40
|
+
| `stream/event` + `stream/concluded` | `CUSTOM plurnk.stream` (payload self-discriminates: event carries `state`, concluded `closeStatus`) |
|
|
41
|
+
| `loop/quiesced` | `CUSTOM plurnk.quiesced` |
|
|
42
|
+
|
|
43
|
+
- **An op row IS a tool call** — its `coordinate` is the `toolCallId`, its tx the args (one
|
|
44
|
+
delta: a dispatched plurnk op is atomic), its rx the result. The log-shaped richness the
|
|
45
|
+
core vocabulary can't hold (fold state, tags, tokens) stays on the row inside
|
|
46
|
+
`plurnk.ambient`/`TOOL_CALL_RESULT` payloads.
|
|
47
|
+
- **The custom namespace** {§agui-custom-namespace} — everything plurnk-specific rides
|
|
48
|
+
`CUSTOM` events named `plurnk.*` (`plurnk.send`, `plurnk.ambient`, `plurnk.proposal`,
|
|
49
|
+
`plurnk.telemetry`, `plurnk.stream`, `plurnk.quiesced`, `plurnk.terminated` — the full loop
|
|
50
|
+
outcome the budget `STATE_DELTA` can't hold). Generic frontends skip unknown customs; plurnk-aware frontends render
|
|
51
|
+
them richly. Nothing plurnk-specific ever masquerades as a core event.
|
|
52
|
+
- **Numbers pass through verbatim** {§agui-numbers-passthrough} — the budget `STATE_DELTA`
|
|
53
|
+
carries the daemon's own usage figures (`contextTokens`, `contextSize` = the effective
|
|
54
|
+
prompt budget, service#345). The bridge never recomputes a number; the daemon's gauge is
|
|
55
|
+
the gauge.
|
|
56
|
+
|
|
57
|
+
- **The row channel** {§agui-row-channel} — every log row ALSO rides `CUSTOM plurnk.row`
|
|
58
|
+
carrying the full wire entry (fold state, tags-in-signal, tokens, coordinate) alongside its
|
|
59
|
+
core projection. Rich clients (TUI/nvim) render plurnk-native fidelity from `plurnk.row`;
|
|
60
|
+
generic clients never see the difference. This is the metadata channel the exclusive-portal
|
|
61
|
+
migration stands on.
|
|
62
|
+
- **The gauge starts true** — `RUN_STARTED` is followed by a `STATE_SNAPSHOT` carrying the
|
|
63
|
+
daemon's `providers.list` truth (the effective prompt budget, the active model), then
|
|
64
|
+
`STATE_DELTA`s. A dropped SSE stream cancels the loop (`loop.cancel`) — the frontend hanging
|
|
65
|
+
up IS the abort signal; no run is orphaned unwatched.
|
|
66
|
+
|
|
67
|
+
- **Reattach replays** {§agui-replay} — a rediscovered thread (the bridge restarted, a second
|
|
68
|
+
frontend arrived) attaches to its existing session by name→id and opens ORIENTED: the model
|
|
69
|
+
run's SENDs replay as `MESSAGES_SNAPSHOT` (the conversation spine; everything else stays
|
|
70
|
+
reachable via live `plurnk.row`), and every pending stop-the-world proposal re-surfaces
|
|
71
|
+
immediately via the daemon's `proposal.list` — the indefinite-wait ruling's client half: a
|
|
72
|
+
days-old question is discoverable, never a mystery hang.
|
|
73
|
+
|
|
74
|
+
## Stop-the-world {§agui-proposal-resolve}
|
|
75
|
+
|
|
76
|
+
Every daemon proposal — file edits, MCP auths, `[300]` operator questions (service#346) —
|
|
77
|
+
surfaces as `CUSTOM plurnk.proposal` carrying `{logEntryId, op, target, body, attrs, flags}`
|
|
78
|
+
(`attrs.question`/`attrs.choices` for questions). The frontend answers via
|
|
79
|
+
`POST /resolve {threadId, logEntryId, decision, body?}` — a passthrough to the daemon's
|
|
80
|
+
`loop.resolve`, where an accept `body` is the answer. The SSE stream stays open while the
|
|
81
|
+
world is stopped — **indefinitely by default** (service ruling: a stopped world waits for its
|
|
82
|
+
human; the timeout is operator opt-in) — and the run resumes on resolution.
|
|
83
|
+
|
|
84
|
+
## The management plane {§agui-management-plane}
|
|
85
|
+
|
|
86
|
+
AG-UI models the RUN plane; the workspace plane rides the charter's ONE escape hatch:
|
|
87
|
+
`POST /plurnk/rpc {threadId, method, params}` — a verbatim JSON-RPC passthrough on the
|
|
88
|
+
thread's own daemon connection (session scoping is exactly the thread's). The daemon's
|
|
89
|
+
method registry is the contract; discover it with `{method: "discover"}`. Session options
|
|
90
|
+
for a thread's FIRST run ride `RunAgentInput.forwardedProps.plurnk` {§agui-forwarded-props}
|
|
91
|
+
(`projectRoot`, `constraints`, `settings` — composing over the bridge's questions default).
|
|
92
|
+
|
|
93
|
+
- **Topology scoping** {§agui-topology-scope} — the session broadcast carries every run's rows
|
|
94
|
+
(workers, the plurnk run, siblings); only the THREAD's model run projects onto the core
|
|
95
|
+
vocabulary. Foreign rows ride `plurnk.row`/`plurnk.ambient` — rich clients render the
|
|
96
|
+
topology; a worker's SEND never masquerades as the assistant speaking.
|
|
97
|
+
- **Concurrent runs: the daemon decides** — a `POST /` while a run is live passes through to
|
|
98
|
+
`loop.run`, whose OWN semantics apply (inject-into-the-active-loop or enqueue — the ack's
|
|
99
|
+
`action` says which). Mechanism, not policy: the bridge adds no queueing of its own; both
|
|
100
|
+
open streams observe the shared session events and end at `loop/terminated`.
|
|
101
|
+
|
|
102
|
+
- **Auth** {§agui-auth} — `PLURNK_AGUI_TOKEN` empty (shipped) = local trust: the loopback bind
|
|
103
|
+
is the boundary, the daemon's own posture. Set, every POST must carry
|
|
104
|
+
`Authorization: Bearer <token>`, checked before any body is read.
|
|
105
|
+
- **Daemon loss fails hard — by design, not omission**: the bridge holds no state the daemon
|
|
106
|
+
doesn't, so a dropped daemon connection kills the affected request loudly and the operator's
|
|
107
|
+
supervisor restarts the bridge (reattach reconstructs every thread by name). Backoff loops
|
|
108
|
+
hide outages; a dead daemon should look dead.
|
|
109
|
+
|
|
110
|
+
## The run endpoint {§agui-run-endpoint}
|
|
111
|
+
|
|
112
|
+
`POST /` (or `/agui`) with an AG-UI `RunAgentInput` body: the last `user` message becomes the
|
|
113
|
+
`loop.run` prompt (`maxTurns`/`flags.yolo` from env); the response is `text/event-stream`,
|
|
114
|
+
one `data:` line per event, ending after `RUN_FINISHED`/`RUN_ERROR`. Yolo never auto-answers
|
|
115
|
+
a question — that's the daemon's own rule; the bridge inherits it.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { AguiEvent, ProposalNotification } from "./types.ts";
|
|
2
|
+
export declare const proposalToolCallId: (logEntryId: number) => string;
|
|
3
|
+
export declare const logEntryIdFromToolCallId: (toolCallId: string) => number | null;
|
|
4
|
+
export declare const proposalToolName: (op: string) => string;
|
|
5
|
+
export declare const proposalToolCall: (p: ProposalNotification) => AguiEvent[];
|
|
6
|
+
export interface ToolResultMessage {
|
|
7
|
+
toolCallId?: string;
|
|
8
|
+
content?: string;
|
|
9
|
+
role?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface Resolution {
|
|
12
|
+
logEntryId: number;
|
|
13
|
+
decision: "accept" | "reject" | "cancel";
|
|
14
|
+
body?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const resolutionFromToolResult: (m: ToolResultMessage) => Resolution | null;
|
|
17
|
+
export interface AguiPlusState {
|
|
18
|
+
providers?: Array<{
|
|
19
|
+
alias: string;
|
|
20
|
+
model: string;
|
|
21
|
+
active: boolean;
|
|
22
|
+
contextSize: number | null;
|
|
23
|
+
}>;
|
|
24
|
+
session?: {
|
|
25
|
+
id: number;
|
|
26
|
+
name: string;
|
|
27
|
+
projectRoot?: string | null;
|
|
28
|
+
budget?: number | null;
|
|
29
|
+
};
|
|
30
|
+
sessions?: Array<{
|
|
31
|
+
id: number;
|
|
32
|
+
name: string;
|
|
33
|
+
}>;
|
|
34
|
+
constraints?: Array<{
|
|
35
|
+
effect: string;
|
|
36
|
+
glob: string;
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
export declare const stateSnapshot: (s: AguiPlusState) => AguiEvent;
|
|
40
|
+
export declare const stateDelta: (patches: Array<{
|
|
41
|
+
op: string;
|
|
42
|
+
path: string;
|
|
43
|
+
value?: unknown;
|
|
44
|
+
}>) => AguiEvent;
|
|
45
|
+
export interface ActionRequest {
|
|
46
|
+
kind: string;
|
|
47
|
+
params: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
export declare const parseAction: (forwardedProps: unknown) => ActionRequest | null;
|
|
50
|
+
export type ActionOutcome = {
|
|
51
|
+
ok: true;
|
|
52
|
+
result?: unknown;
|
|
53
|
+
} | {
|
|
54
|
+
ok: false;
|
|
55
|
+
error: string;
|
|
56
|
+
};
|
|
57
|
+
export declare const actionResult: (kind: string, outcome: ActionOutcome) => AguiEvent;
|
|
58
|
+
//# sourceMappingURL=AguiPlus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AguiPlus.d.ts","sourceRoot":"","sources":["../src/AguiPlus.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAKlE,eAAO,MAAM,kBAAkB,eAAgB,MAAM,KAAG,MAA8B,CAAC;AACvF,eAAO,MAAM,wBAAwB,eAAgB,MAAM,KAAG,MAAM,GAAG,IAGtE,CAAC;AAOF,eAAO,MAAM,gBAAgB,OAAQ,MAAM,KAAG,MAAqE,CAAC;AAIpH,eAAO,MAAM,gBAAgB,MAAO,oBAAoB,KAAG,SAAS,EAOnE,CAAC;AAKF,MAAM,WAAW,iBAAiB;IAAG,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE;AAC3F,MAAM,WAAW,UAAU;IAAG,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE;AAC3G,eAAO,MAAM,wBAAwB,MAAO,iBAAiB,KAAG,UAAU,GAAG,IAiB5E,CAAC;AAIF,MAAM,WAAW,aAAa;IAC1B,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IACjG,OAAO,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC5F,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzD;AACD,eAAO,MAAM,aAAa,MAAO,aAAa,KAAG,SAAkE,CAAC;AACpH,eAAO,MAAM,UAAU,YAAa,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,KAAG,SAAsD,CAAC;AASlJ,MAAM,WAAW,aAAa;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE;AAChF,eAAO,MAAM,WAAW,mBAAoB,OAAO,KAAG,aAAa,GAAG,IAOrE,CAAC;AACF,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAC1F,eAAO,MAAM,YAAY,SAAU,MAAM,WAAW,aAAa,KAAG,SACe,CAAC"}
|
package/dist/AguiPlus.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// The AG-UI+ projection (single-interface rendering) — prototyped + unit-tested
|
|
2
|
+
// standalone ahead of the in-process module (plurnk-agui#2, WS-1). Pure functions:
|
|
3
|
+
// engine state → AG-UI events, and the inverse resume-mapping. No transport, no I/O
|
|
4
|
+
// — so it ports into the in-process module unchanged when the seam lands.
|
|
5
|
+
//
|
|
6
|
+
// §1 proposals/[300] questions → tool-calls (terminate-resume HITL); §2 reads →
|
|
7
|
+
// shared STATE. This is the flagship choreography de-risked as logic before code.
|
|
8
|
+
// ── §1 — stop-the-world → tool-call ──────────────────────────────────
|
|
9
|
+
// toolCallId correlates the TERMINATING run's TOOL_CALL with the RESUME run's
|
|
10
|
+
// tool-result → the exact pending proposal. Encodes the logEntryId: `prop:<id>`.
|
|
11
|
+
export const proposalToolCallId = (logEntryId) => `prop:${logEntryId}`;
|
|
12
|
+
export const logEntryIdFromToolCallId = (toolCallId) => {
|
|
13
|
+
const m = /^prop:(\d+)$/.exec(toolCallId);
|
|
14
|
+
return m === null ? null : Number(m[1]);
|
|
15
|
+
};
|
|
16
|
+
// Tool-call NAME — AG-UI terminology all the way up to the seam (operator ruling,
|
|
17
|
+
// 2026-07-09): the client-facing name is AG-UI-conventional, NOT plurnk-namespaced.
|
|
18
|
+
// A side-effecting proposal is an approval request; a [300] question elicits input.
|
|
19
|
+
// The plurnk correlation rides the opaque toolCallId (`prop:<logEntryId>`), so the
|
|
20
|
+
// generic names carry no plurnk vocabulary upward.
|
|
21
|
+
export const proposalToolName = (op) => (op === "SEND" ? "request_user_input" : "request_approval");
|
|
22
|
+
// The run's tail when it hits a pause: the tool-call, then the CALLER emits
|
|
23
|
+
// RUN_FINISHED to terminate. The loop stays paused in-engine — untouched.
|
|
24
|
+
export const proposalToolCall = (p) => {
|
|
25
|
+
const toolCallId = proposalToolCallId(p.logEntryId);
|
|
26
|
+
return [
|
|
27
|
+
{ type: "TOOL_CALL_START", toolCallId, toolCallName: proposalToolName(p.op) },
|
|
28
|
+
{ type: "TOOL_CALL_ARGS", toolCallId, delta: JSON.stringify({ op: p.op, target: p.target, body: p.body, attrs: p.attrs, staleClobberRisk: p.staleClobberRisk ?? false }) },
|
|
29
|
+
{ type: "TOOL_CALL_END", toolCallId },
|
|
30
|
+
];
|
|
31
|
+
};
|
|
32
|
+
export const resolutionFromToolResult = (m) => {
|
|
33
|
+
if (typeof m.toolCallId !== "string")
|
|
34
|
+
return null;
|
|
35
|
+
const logEntryId = logEntryIdFromToolCallId(m.toolCallId);
|
|
36
|
+
if (logEntryId === null)
|
|
37
|
+
return null;
|
|
38
|
+
let decision;
|
|
39
|
+
let body;
|
|
40
|
+
if (typeof m.content === "string" && m.content.length > 0) {
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(m.content);
|
|
43
|
+
decision = parsed.decision;
|
|
44
|
+
body = parsed.body;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
decision = m.content.trim(); // tolerate a bare decision string
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (decision !== "accept" && decision !== "reject" && decision !== "cancel")
|
|
51
|
+
return null;
|
|
52
|
+
return { logEntryId, decision, ...(body !== undefined ? { body } : {}) };
|
|
53
|
+
};
|
|
54
|
+
export const stateSnapshot = (s) => ({ type: "STATE_SNAPSHOT", snapshot: { plurnk: s } });
|
|
55
|
+
export const stateDelta = (patches) => ({ type: "STATE_DELTA", delta: patches });
|
|
56
|
+
export const parseAction = (forwardedProps) => {
|
|
57
|
+
const action = forwardedProps?.plurnk?.action;
|
|
58
|
+
if (action === null || typeof action !== "object")
|
|
59
|
+
return null;
|
|
60
|
+
const kind = action.kind;
|
|
61
|
+
if (typeof kind !== "string" || kind.length === 0)
|
|
62
|
+
return null;
|
|
63
|
+
const { kind: _kind, ...params } = action;
|
|
64
|
+
return { kind, params };
|
|
65
|
+
};
|
|
66
|
+
export const actionResult = (kind, outcome) => ({ type: "CUSTOM", name: "plurnk.action.result", value: { kind, ...outcome } });
|
|
67
|
+
//# sourceMappingURL=AguiPlus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AguiPlus.js","sourceRoot":"","sources":["../src/AguiPlus.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default class DaemonClient {
|
|
2
|
+
#private;
|
|
3
|
+
private constructor();
|
|
4
|
+
static connect(url: string): Promise<DaemonClient>;
|
|
5
|
+
call<T = unknown>(method: string, params?: object): Promise<T>;
|
|
6
|
+
on(method: string, fn: (params: unknown) => void): () => void;
|
|
7
|
+
close(): void;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=DaemonClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DaemonClient.d.ts","sourceRoot":"","sources":["../src/DaemonClient.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,OAAO,OAAO,YAAY;;IAM7B,OAAO,eAeN;IAED,OAAa,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAOvD;IAED,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAMjE;IAED,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAK5D;IAED,KAAK,IAAI,IAAI,CAEZ;CACJ"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// An ordinary client of the plurnk daemon's JSON-RPC-over-WebSocket wire — the same protocol
|
|
2
|
+
// the CLI/TUI/nvim clients speak. The bridge holds no state the daemon doesn't (§agui-daemon-client).
|
|
3
|
+
export default class DaemonClient {
|
|
4
|
+
#ws;
|
|
5
|
+
#nextId = 1;
|
|
6
|
+
#pending = new Map();
|
|
7
|
+
#listeners = new Map();
|
|
8
|
+
constructor(ws) {
|
|
9
|
+
this.#ws = ws;
|
|
10
|
+
ws.addEventListener("message", (ev) => {
|
|
11
|
+
const msg = JSON.parse(String(ev.data));
|
|
12
|
+
if (typeof msg.id === "number" && this.#pending.has(msg.id)) {
|
|
13
|
+
const waiter = this.#pending.get(msg.id);
|
|
14
|
+
this.#pending.delete(msg.id);
|
|
15
|
+
if (msg.error !== undefined)
|
|
16
|
+
waiter.reject(new Error(msg.error.message));
|
|
17
|
+
else
|
|
18
|
+
waiter.resolve(msg.result);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (typeof msg.method === "string") {
|
|
22
|
+
for (const fn of this.#listeners.get(msg.method) ?? [])
|
|
23
|
+
fn(msg.params);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
static async connect(url) {
|
|
28
|
+
const ws = new WebSocket(url);
|
|
29
|
+
await new Promise((resolve, reject) => {
|
|
30
|
+
ws.addEventListener("open", () => resolve(), { once: true });
|
|
31
|
+
ws.addEventListener("error", () => reject(new Error(`plurnk-agui: cannot reach the daemon at ${url} — is plurnk-service running?`)), { once: true });
|
|
32
|
+
});
|
|
33
|
+
return new DaemonClient(ws);
|
|
34
|
+
}
|
|
35
|
+
call(method, params = {}) {
|
|
36
|
+
const id = this.#nextId++;
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
this.#pending.set(id, { resolve: resolve, reject });
|
|
39
|
+
this.#ws.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
on(method, fn) {
|
|
43
|
+
let set = this.#listeners.get(method);
|
|
44
|
+
if (set === undefined) {
|
|
45
|
+
set = new Set();
|
|
46
|
+
this.#listeners.set(method, set);
|
|
47
|
+
}
|
|
48
|
+
set.add(fn);
|
|
49
|
+
return () => { set.delete(fn); };
|
|
50
|
+
}
|
|
51
|
+
close() {
|
|
52
|
+
this.#ws.close();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=DaemonClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DaemonClient.js","sourceRoot":"","sources":["../src/DaemonClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export type ProposalDecision = "accept" | "reject" | "cancel";
|
|
2
|
+
export interface ProposalResolution {
|
|
3
|
+
decision: ProposalDecision;
|
|
4
|
+
body?: string;
|
|
5
|
+
outcome?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PendingProposal {
|
|
8
|
+
logEntryId: number;
|
|
9
|
+
runId: number;
|
|
10
|
+
loopId: number;
|
|
11
|
+
turnId: number;
|
|
12
|
+
op: string;
|
|
13
|
+
suffix: string;
|
|
14
|
+
scheme: string | null;
|
|
15
|
+
pathname: string | null;
|
|
16
|
+
tx: string | null;
|
|
17
|
+
attrs: string | null;
|
|
18
|
+
}
|
|
19
|
+
export interface PlurnkStatement {
|
|
20
|
+
op: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
export type LogEntryWire = Record<string, unknown>;
|
|
24
|
+
export interface DaemonSeam {
|
|
25
|
+
subscribeToEvents(handler: (sessionId: number | null, method: string, params: unknown) => void): () => void;
|
|
26
|
+
pendingProposals(sessionId: number): Promise<PendingProposal[]>;
|
|
27
|
+
resolveProposal(logEntryId: number, resolution: ProposalResolution): void;
|
|
28
|
+
runLoop(args: {
|
|
29
|
+
sessionId: number;
|
|
30
|
+
runId: number;
|
|
31
|
+
prompt: string;
|
|
32
|
+
maxTurns?: number;
|
|
33
|
+
flags?: {
|
|
34
|
+
yolo?: boolean;
|
|
35
|
+
};
|
|
36
|
+
openPaths?: string[];
|
|
37
|
+
}): Promise<{
|
|
38
|
+
action: "injected_next_turn" | "enqueued_new_loop";
|
|
39
|
+
loopId: number;
|
|
40
|
+
turnSeq?: number;
|
|
41
|
+
}>;
|
|
42
|
+
cancelDrain(runId: number, reason?: string): boolean;
|
|
43
|
+
dispatchAsClient(args: {
|
|
44
|
+
sessionId: number;
|
|
45
|
+
runId: number;
|
|
46
|
+
statement: PlurnkStatement;
|
|
47
|
+
}): Promise<{
|
|
48
|
+
status: number;
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
}>;
|
|
51
|
+
readLog(args: {
|
|
52
|
+
sessionId: number;
|
|
53
|
+
runId: number;
|
|
54
|
+
loopId?: number;
|
|
55
|
+
turnId?: number;
|
|
56
|
+
sinceId?: number;
|
|
57
|
+
limit?: number;
|
|
58
|
+
loopSeq?: number;
|
|
59
|
+
turnSeq?: number;
|
|
60
|
+
sequence?: number;
|
|
61
|
+
}): Promise<LogEntryWire[]>;
|
|
62
|
+
listProviders(): {
|
|
63
|
+
aliases: Array<{
|
|
64
|
+
alias: string;
|
|
65
|
+
provider: string;
|
|
66
|
+
model: string;
|
|
67
|
+
active: boolean;
|
|
68
|
+
contextSize: number | null;
|
|
69
|
+
}>;
|
|
70
|
+
};
|
|
71
|
+
createSession(args: {
|
|
72
|
+
name?: string;
|
|
73
|
+
projectRoot?: string | null;
|
|
74
|
+
settings?: string;
|
|
75
|
+
constraints?: Array<{
|
|
76
|
+
effect: string;
|
|
77
|
+
glob: string;
|
|
78
|
+
}>;
|
|
79
|
+
}): Promise<ClientEnvelope>;
|
|
80
|
+
attachSession(args: {
|
|
81
|
+
sessionId: number;
|
|
82
|
+
runId?: number;
|
|
83
|
+
runName?: string;
|
|
84
|
+
}): Promise<ClientEnvelope>;
|
|
85
|
+
listSessions(): Promise<Array<{
|
|
86
|
+
id: number;
|
|
87
|
+
name: string;
|
|
88
|
+
}>>;
|
|
89
|
+
listRuns(sessionId: number): Promise<Array<{
|
|
90
|
+
id: number;
|
|
91
|
+
name: string;
|
|
92
|
+
}>>;
|
|
93
|
+
}
|
|
94
|
+
export interface ClientEnvelope {
|
|
95
|
+
sessionId: number;
|
|
96
|
+
sessionName: string;
|
|
97
|
+
projectRoot: string | null;
|
|
98
|
+
runId: number;
|
|
99
|
+
runName: string;
|
|
100
|
+
modelRunId: number | null;
|
|
101
|
+
clientLoopId: number | null;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=DaemonSeam.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DaemonSeam.d.ts","sourceRoot":"","sources":["../src/DaemonSeam.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// My coupling to the daemon's in-process client-interface seam (service#355). The
|
|
2
|
+
// module depends on THIS interface — the committed, tested contract — never the
|
|
3
|
+
// Daemon class's guts. It grows as the service lands hooks (loop-control,
|
|
4
|
+
// session/envelope, reads, fork, execs/auth, the boot plug-point); today it carries
|
|
5
|
+
// what's published: the event source (B) and proposal read + resolve (C + A-resolve).
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=DaemonSeam.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DaemonSeam.js","sourceRoot":"","sources":["../src/DaemonSeam.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,gFAAgF;AAChF,0EAA0E;AAC1E,oFAAoF;AACpF,sFAAsF"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AguiEvent } from "./types.ts";
|
|
2
|
+
export default class EventRouter {
|
|
3
|
+
#private;
|
|
4
|
+
constructor(args: {
|
|
5
|
+
threadId: string;
|
|
6
|
+
runId: string;
|
|
7
|
+
modelRunId?: number | null;
|
|
8
|
+
sessionId?: number | null;
|
|
9
|
+
});
|
|
10
|
+
runStarted(snapshot?: unknown): AguiEvent[];
|
|
11
|
+
replay(entries: Array<Record<string, unknown>>): AguiEvent[];
|
|
12
|
+
route(method: string, params: unknown): AguiEvent[];
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=EventRouter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventRouter.d.ts","sourceRoot":"","sources":["../src/EventRouter.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAgD,MAAM,YAAY,CAAC;AAE1F,MAAM,CAAC,OAAO,OAAO,WAAW;;IAG5B,YAAY,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,EAE3G;IAED,UAAU,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,EAAE,CAAyC;IACpF,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,EAAE,CAAoC;IAEhG,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,SAAS,EAAE,CAUlD;CACJ"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// The module's per-run render core (plurnk-agui#2). Routes a daemon event
|
|
2
|
+
// (method, params) from the seam's event source → AG-UI events, composing the proven
|
|
3
|
+
// projections: log/entry → core vocab, loop/terminated → RUN_FINISHED + budget STATE,
|
|
4
|
+
// telemetry/stream → CUSTOM. Per-run state (turn tracking, model-run binding) lives in
|
|
5
|
+
// the Translator. Proposals are ProposalHitl's domain (the terminate-resume tool-call),
|
|
6
|
+
// so this router deliberately leaves loop/proposal to it — one owner per concern.
|
|
7
|
+
import Translator from "./Translator.js";
|
|
8
|
+
export default class EventRouter {
|
|
9
|
+
#t;
|
|
10
|
+
constructor(args) {
|
|
11
|
+
this.#t = new Translator(args);
|
|
12
|
+
}
|
|
13
|
+
runStarted(snapshot) { return this.#t.runStarted(snapshot); }
|
|
14
|
+
replay(entries) { return this.#t.replay(entries); }
|
|
15
|
+
route(method, params) {
|
|
16
|
+
switch (method) {
|
|
17
|
+
case "log/entry": return this.#t.logEntry(params);
|
|
18
|
+
case "loop/terminated": return this.#t.terminated(params);
|
|
19
|
+
case "telemetry/event": return this.#t.telemetry(params.event ?? params);
|
|
20
|
+
case "stream/event":
|
|
21
|
+
case "stream/concluded": return [{ type: "CUSTOM", name: "plurnk.stream", value: params }];
|
|
22
|
+
case "loop/proposal": return []; // ProposalHitl owns HITL (terminate-resume tool-call)
|
|
23
|
+
default: return []; // session/created + anything unmodeled: the module handles out-of-band
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=EventRouter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventRouter.js","sourceRoot":"","sources":["../src/EventRouter.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,qFAAqF;AACrF,sFAAsF;AACtF,uFAAuF;AACvF,wFAAwF;AACxF,kFAAkF;AAElF,OAAO,UAAU,MAAM,iBAAiB,CAAC;AAGzC,MAAM,CAAC,OAAO,OAAO,WAAW;IAC5B,EAAE,CAAa;IAEf,YAAY,IAAgG;QACxG,IAAI,CAAC,EAAE,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,UAAU,CAAC,QAAkB,IAAiB,OAAO,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACpF,MAAM,CAAC,OAAuC,IAAiB,OAAO,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAEhG,KAAK,CAAC,MAAc,EAAE,MAAe;QACjC,QAAQ,MAAM,EAAE,CAAC;YACb,KAAK,WAAW,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,MAA8B,CAAC,CAAC;YAC1E,KAAK,iBAAiB,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAgC,CAAC,CAAC;YACpF,KAAK,iBAAiB,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC,SAAS,CAAE,MAA8B,CAAC,KAAK,IAAI,MAAM,CAAC,CAAC;YAClG,KAAK,cAAc,CAAC;YACpB,KAAK,kBAAkB,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;YAC3F,KAAK,eAAe,EAAE,OAAO,EAAE,CAAC,CAAC,sDAAsD;YACvF,SAAS,OAAO,EAAE,CAAC,CAAC,uEAAuE;QAC/F,CAAC;IACL,CAAC;CACJ"}
|
package/dist/Module.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DaemonSeam } from "./DaemonSeam.ts";
|
|
2
|
+
export interface ModuleOptions {
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
sessionPrefix: string;
|
|
6
|
+
token?: string;
|
|
7
|
+
maxTurns?: number;
|
|
8
|
+
}
|
|
9
|
+
export default class Module {
|
|
10
|
+
#private;
|
|
11
|
+
constructor(seam: DaemonSeam, opts: ModuleOptions);
|
|
12
|
+
static init(opts: ModuleOptions): (seam: DaemonSeam) => Promise<Module>;
|
|
13
|
+
listen(): Promise<{
|
|
14
|
+
host: string;
|
|
15
|
+
port: number;
|
|
16
|
+
}>;
|
|
17
|
+
address(): {
|
|
18
|
+
host: string;
|
|
19
|
+
port: number;
|
|
20
|
+
};
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=Module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Module.d.ts","sourceRoot":"","sources":["../src/Module.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,iBAAiB,CAAC;AAGlE,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,CAAC,OAAO,OAAO,MAAM;;IAOvB,YAAY,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,EAKhD;IAGD,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,GAAG,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,MAAM,CAAC,CAMtE;IAEK,MAAM,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAMtD;IAED,OAAO,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAIxC;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAG3B;CAwIJ"}
|