appduct 0.10.0 → 0.11.1
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/README.md +122 -52
- package/dist/artifact-inspect.d.ts +0 -1
- package/dist/bin.d.ts +0 -1
- package/dist/bin.js +1250 -2
- package/dist/cli/command-options.d.ts +8 -1
- package/dist/cli/create-cli.d.ts +0 -1
- package/dist/cli/dispatch.d.ts +10 -1
- package/dist/cli/envelope.d.ts +19 -0
- package/dist/cli/global-flags.d.ts +30 -0
- package/dist/cli/open-target.d.ts +25 -8
- package/dist/cli/result-types.d.ts +31 -5
- package/dist/cli/router.d.ts +78 -0
- package/dist/cli/routes/daemon/index.d.ts +9 -0
- package/dist/cli/routes/daemon/index.js +848 -0
- package/dist/cli/routes/daemon/run.d.ts +3 -0
- package/dist/cli/routes/daemon/run.js +3246 -0
- package/dist/cli/routes/daemon/start.d.ts +3 -0
- package/dist/cli/routes/daemon/start.js +1677 -0
- package/dist/cli/routes/daemon/status.d.ts +4 -0
- package/dist/cli/routes/daemon/status.js +1685 -0
- package/dist/cli/routes/daemon/stop.d.ts +4 -0
- package/dist/cli/routes/daemon/stop.js +1655 -0
- package/dist/cli/routes/doctor.d.ts +3 -0
- package/dist/cli/routes/doctor.js +1075 -0
- package/dist/cli/routes/events.d.ts +3 -0
- package/dist/cli/routes/events.js +1915 -0
- package/dist/cli/routes/init.d.ts +3 -0
- package/dist/cli/routes/init.js +1486 -0
- package/dist/cli/routes/invoke.d.ts +3 -0
- package/dist/cli/routes/invoke.js +1893 -0
- package/dist/cli/routes/keygen.d.ts +3 -0
- package/dist/cli/routes/keygen.js +928 -0
- package/dist/cli/routes/link.d.ts +3 -0
- package/dist/cli/routes/link.js +2714 -0
- package/dist/cli/routes/ls.d.ts +3 -0
- package/dist/cli/routes/ls.js +1668 -0
- package/dist/cli/routes/mcp.d.ts +5 -0
- package/dist/cli/routes/mcp.js +3929 -0
- package/dist/cli/routes/revoke.d.ts +3 -0
- package/dist/cli/routes/revoke.js +1684 -0
- package/dist/cli/routes/tools.d.ts +3 -0
- package/dist/cli/routes/tools.js +1860 -0
- package/dist/cli/runner.d.ts +3 -11
- package/dist/cli/types.d.ts +9 -6
- package/dist/cli/version-guard.d.ts +20 -0
- package/dist/cli.d.ts +0 -1
- package/dist/client/app-client.d.ts +4 -4
- package/dist/client/bootstrap.d.ts +4 -4
- package/dist/client/connect.d.ts +0 -1
- package/dist/client/errors.d.ts +0 -1
- package/dist/client/index.d.ts +1 -2
- package/dist/client/index.js +2389 -16
- package/dist/commands/daemon/context.d.ts +14 -0
- package/dist/commands/daemon/run.d.ts +11 -0
- package/dist/commands/daemon/start.d.ts +5 -0
- package/dist/commands/daemon/status.d.ts +5 -0
- package/dist/commands/daemon/stop.d.ts +5 -0
- package/dist/commands/doctor.d.ts +0 -1
- package/dist/commands/events.d.ts +0 -1
- package/dist/commands/init.d.ts +17 -4
- package/dist/commands/invoke.d.ts +0 -1
- package/dist/commands/keygen.d.ts +0 -1
- package/dist/commands/link.d.ts +1 -2
- package/dist/commands/ls.d.ts +0 -1
- package/dist/commands/mcp.d.ts +2 -4
- package/dist/commands/revoke.d.ts +0 -1
- package/dist/commands/tools.d.ts +19 -3
- package/dist/daemon/address.d.ts +0 -1
- package/dist/daemon/audit.d.ts +6 -11
- package/dist/daemon/calls.d.ts +0 -1
- package/dist/daemon/config.d.ts +13 -16
- package/dist/daemon/daemon.d.ts +0 -1
- package/dist/daemon/event-bus.d.ts +0 -1
- package/dist/daemon/links.d.ts +0 -1
- package/dist/daemon/listener.d.ts +0 -1
- package/dist/daemon/log-rotation.d.ts +0 -1
- package/dist/daemon/pidfile.d.ts +14 -8
- package/dist/daemon/policy.d.ts +0 -1
- package/dist/daemon/registry.d.ts +0 -1
- package/dist/daemon/rpc-errors.d.ts +16 -0
- package/dist/daemon/rpc-server.d.ts +0 -11
- package/dist/daemon/sessions.d.ts +0 -1
- package/dist/daemon/socket-probe.d.ts +0 -1
- package/dist/daemon/state-dir.d.ts +0 -1
- package/dist/daemon/timers.d.ts +0 -1
- package/dist/daemon/tls.d.ts +0 -1
- package/dist/errors.d.ts +0 -1
- package/dist/host-certificate.d.ts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1307 -3
- package/dist/key-material.d.ts +0 -1
- package/dist/link.d.ts +8 -7
- package/dist/mcp/app-tools.d.ts +153 -0
- package/dist/mcp/connect-tool.d.ts +14 -8
- package/dist/mcp/daemon-tools.d.ts +2 -11
- package/dist/mcp/events-tool.d.ts +0 -1
- package/dist/mcp/server.d.ts +33 -14
- package/dist/native-scheme.d.ts +0 -1
- package/dist/output.d.ts +8 -11
- package/dist/package-root.d.ts +11 -0
- package/dist/package-version.d.ts +0 -1
- package/dist/qr-terminal.d.ts +0 -1
- package/dist/rpc/client.d.ts +3 -3
- package/dist/scheme.d.ts +60 -2
- package/dist/spki-pin.d.ts +0 -1
- package/package.json +6 -3
- package/dist/artifact-inspect.d.ts.map +0 -1
- package/dist/artifact-inspect.js +0 -334
- package/dist/bin.d.ts.map +0 -1
- package/dist/cli/command-options.d.ts.map +0 -1
- package/dist/cli/command-options.js +0 -95
- package/dist/cli/create-cli.d.ts.map +0 -1
- package/dist/cli/create-cli.js +0 -56
- package/dist/cli/dispatch.d.ts.map +0 -1
- package/dist/cli/dispatch.js +0 -275
- package/dist/cli/open-target.d.ts.map +0 -1
- package/dist/cli/open-target.js +0 -438
- package/dist/cli/result-types.d.ts.map +0 -1
- package/dist/cli/result-types.js +0 -6
- package/dist/cli/runner.d.ts.map +0 -1
- package/dist/cli/runner.js +0 -111
- package/dist/cli/types.d.ts.map +0 -1
- package/dist/cli/types.js +0 -10
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -2
- package/dist/client/app-client.d.ts.map +0 -1
- package/dist/client/app-client.js +0 -223
- package/dist/client/bootstrap.d.ts.map +0 -1
- package/dist/client/bootstrap.js +0 -108
- package/dist/client/connect.d.ts.map +0 -1
- package/dist/client/connect.js +0 -38
- package/dist/client/errors.d.ts.map +0 -1
- package/dist/client/errors.js +0 -40
- package/dist/client/index.d.ts.map +0 -1
- package/dist/commands/daemon.d.ts +0 -22
- package/dist/commands/daemon.d.ts.map +0 -1
- package/dist/commands/daemon.js +0 -128
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js +0 -42
- package/dist/commands/events.d.ts.map +0 -1
- package/dist/commands/events.js +0 -76
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -231
- package/dist/commands/invoke.d.ts.map +0 -1
- package/dist/commands/invoke.js +0 -54
- package/dist/commands/keygen.d.ts.map +0 -1
- package/dist/commands/keygen.js +0 -46
- package/dist/commands/link.d.ts.map +0 -1
- package/dist/commands/link.js +0 -43
- package/dist/commands/ls.d.ts.map +0 -1
- package/dist/commands/ls.js +0 -7
- package/dist/commands/mcp.d.ts.map +0 -1
- package/dist/commands/mcp.js +0 -98
- package/dist/commands/revoke.d.ts.map +0 -1
- package/dist/commands/revoke.js +0 -7
- package/dist/commands/tools.d.ts.map +0 -1
- package/dist/commands/tools.js +0 -52
- package/dist/daemon/address.d.ts.map +0 -1
- package/dist/daemon/address.js +0 -71
- package/dist/daemon/audit.d.ts.map +0 -1
- package/dist/daemon/audit.js +0 -237
- package/dist/daemon/calls.d.ts.map +0 -1
- package/dist/daemon/calls.js +0 -158
- package/dist/daemon/config.d.ts.map +0 -1
- package/dist/daemon/config.js +0 -170
- package/dist/daemon/daemon.d.ts.map +0 -1
- package/dist/daemon/daemon.js +0 -560
- package/dist/daemon/event-bus.d.ts.map +0 -1
- package/dist/daemon/event-bus.js +0 -111
- package/dist/daemon/links.d.ts.map +0 -1
- package/dist/daemon/links.js +0 -118
- package/dist/daemon/listener.d.ts.map +0 -1
- package/dist/daemon/listener.js +0 -132
- package/dist/daemon/log-rotation.d.ts.map +0 -1
- package/dist/daemon/log-rotation.js +0 -136
- package/dist/daemon/pidfile.d.ts.map +0 -1
- package/dist/daemon/pidfile.js +0 -90
- package/dist/daemon/policy.d.ts.map +0 -1
- package/dist/daemon/policy.js +0 -24
- package/dist/daemon/registry.d.ts.map +0 -1
- package/dist/daemon/registry.js +0 -31
- package/dist/daemon/rpc-server.d.ts.map +0 -1
- package/dist/daemon/rpc-server.js +0 -203
- package/dist/daemon/sessions.d.ts.map +0 -1
- package/dist/daemon/sessions.js +0 -429
- package/dist/daemon/socket-probe.d.ts.map +0 -1
- package/dist/daemon/socket-probe.js +0 -28
- package/dist/daemon/state-dir.d.ts.map +0 -1
- package/dist/daemon/state-dir.js +0 -43
- package/dist/daemon/timers.d.ts.map +0 -1
- package/dist/daemon/timers.js +0 -12
- package/dist/daemon/tls.d.ts.map +0 -1
- package/dist/daemon/tls.js +0 -79
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -162
- package/dist/host-certificate.d.ts.map +0 -1
- package/dist/host-certificate.js +0 -111
- package/dist/index.d.ts.map +0 -1
- package/dist/key-material.d.ts.map +0 -1
- package/dist/key-material.js +0 -42
- package/dist/link.d.ts.map +0 -1
- package/dist/link.js +0 -117
- package/dist/mcp/connect-tool.d.ts.map +0 -1
- package/dist/mcp/connect-tool.js +0 -396
- package/dist/mcp/daemon-tools.d.ts.map +0 -1
- package/dist/mcp/daemon-tools.js +0 -29
- package/dist/mcp/events-tool.d.ts.map +0 -1
- package/dist/mcp/events-tool.js +0 -322
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js +0 -538
- package/dist/mcp/tool-mapping.d.ts +0 -58
- package/dist/mcp/tool-mapping.d.ts.map +0 -1
- package/dist/mcp/tool-mapping.js +0 -143
- package/dist/mcp/tool-namespace.d.ts +0 -40
- package/dist/mcp/tool-namespace.d.ts.map +0 -1
- package/dist/mcp/tool-namespace.js +0 -65
- package/dist/native-scheme.d.ts.map +0 -1
- package/dist/native-scheme.js +0 -465
- package/dist/output.d.ts.map +0 -1
- package/dist/output.js +0 -342
- package/dist/package-version.d.ts.map +0 -1
- package/dist/package-version.js +0 -47
- package/dist/qr-terminal.d.ts.map +0 -1
- package/dist/qr-terminal.js +0 -45
- package/dist/rpc/client.d.ts.map +0 -1
- package/dist/rpc/client.js +0 -819
- package/dist/scheme.d.ts.map +0 -1
- package/dist/scheme.js +0 -321
- package/dist/spki-pin.d.ts.map +0 -1
- package/dist/spki-pin.js +0 -14
package/dist/mcp/events-tool.js
DELETED
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The built-in `appduct_events` / `appduct_wait_for_event` MCP tools (issue #6): a pull
|
|
3
|
-
* surface over the daemon's `app_event` plumbing (`postEvent()` on the app side, `events.since`'s
|
|
4
|
-
* per-session retention buffer on the daemon side — see `daemon/event-bus.ts`). MCP is
|
|
5
|
-
* request/response, so an agent otherwise has no way to observe anything the app pushes.
|
|
6
|
-
*
|
|
7
|
-
* `appduct_wait_for_event` drains the retained buffer for an already-arrived match before
|
|
8
|
-
* falling back to a live wait, exactly to avoid the race `appduct_wait_for_session` doesn't
|
|
9
|
-
* have to worry about (a session is either claimed or it isn't, but an `app_event` can easily fire
|
|
10
|
-
* between "the agent decides to wait" and "the wait subscription lands"). The live notification
|
|
11
|
-
* listener is registered *before* the `events.since` drain call is even sent — not merely before
|
|
12
|
-
* it resolves — because the daemon's response to that call and a subsequent `notify()` for a new
|
|
13
|
-
* event can arrive in the same socket chunk; `rpc/client.ts` fans a chunk's notification lines out
|
|
14
|
-
* to whatever listeners exist at the moment it's processed, so a listener added only after `await
|
|
15
|
-
* stream.call(eventsSince, …)` returns can miss an event that was already in that same chunk.
|
|
16
|
-
* Arrivals before the drain has been merged into the backlog are buffered, then merged in
|
|
17
|
-
* (deduped by `seq`) before falling through to the live phase — see `handleWaitForEventTool` below.
|
|
18
|
-
*/
|
|
19
|
-
import { RPC_METHODS, EVENT_KINDS, } from "@appduct/shared";
|
|
20
|
-
import { openDaemonStream } from "../rpc/client.js";
|
|
21
|
-
import { McpBuiltinToolError } from "./connect-tool.js";
|
|
22
|
-
export const EVENTS_TOOL_NAME = "appduct_events";
|
|
23
|
-
export const WAIT_FOR_EVENT_TOOL_NAME = "appduct_wait_for_event";
|
|
24
|
-
const DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MS = 120_000;
|
|
25
|
-
/** Kept safely under the 30-minute idle window a stdio MCP server gets before Claude Code aborts a
|
|
26
|
-
* tool call that has sent neither a response nor a progress notification (issue #6's "Constraint on
|
|
27
|
-
* the blocking wait tool") — with margin for RPC round-trip and scheduling delay. */
|
|
28
|
-
const MAX_WAIT_FOR_EVENT_TIMEOUT_MS = 25 * 60 * 1000;
|
|
29
|
-
/** How often a progress notification is emitted during a live wait, when the caller supplied a
|
|
30
|
-
* progress token — informational only (the timeout cap above is what actually keeps the call
|
|
31
|
-
* within the idle window), but it gives a caller watching progress something to show. */
|
|
32
|
-
const WAIT_FOR_EVENT_PROGRESS_INTERVAL_MS = 60_000;
|
|
33
|
-
const KNOWN_EVENT_KINDS = new Set(EVENT_KINDS);
|
|
34
|
-
/** Server-side cap on `timeoutMs` (issue #6's "Cap timeoutMs server-side below the idle window
|
|
35
|
-
* rather than trusting the caller") — a pure function so the cap's boundary behavior is
|
|
36
|
-
* unit-testable without spinning up a daemon or actually waiting out either bound. */
|
|
37
|
-
export const clampWaitForEventTimeoutMs = (requestedMs) => {
|
|
38
|
-
return Math.min(requestedMs ?? DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MS, MAX_WAIT_FOR_EVENT_TIMEOUT_MS);
|
|
39
|
-
};
|
|
40
|
-
export const EVENTS_TOOL_DESCRIPTOR = {
|
|
41
|
-
name: EVENTS_TOOL_NAME,
|
|
42
|
-
description: "Drain events retained in the daemon's per-session ring buffer since a cursor — the pull " +
|
|
43
|
-
"counterpart to a live subscription, for checking what happened after calling a tool or " +
|
|
44
|
-
"triggering app behavior. With no selector, targets the sole active/suspended session. " +
|
|
45
|
-
"Returns { events, cursor }; pass cursor back as since on the next call to avoid re-reading " +
|
|
46
|
-
"events you've already seen. limit (if given) keeps the OLDEST events in the window and " +
|
|
47
|
-
"advances cursor only past what was actually returned, so repeated calls page forward through " +
|
|
48
|
-
"everything retained rather than skipping ahead.",
|
|
49
|
-
inputSchema: {
|
|
50
|
-
type: "object",
|
|
51
|
-
properties: {
|
|
52
|
-
selector: { type: "string" },
|
|
53
|
-
since: { type: "integer", minimum: 0 },
|
|
54
|
-
kinds: { type: "array", items: { type: "string", enum: EVENT_KINDS } },
|
|
55
|
-
limit: { type: "integer", exclusiveMinimum: 0 },
|
|
56
|
-
},
|
|
57
|
-
additionalProperties: false,
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
export const WAIT_FOR_EVENT_TOOL_DESCRIPTOR = {
|
|
61
|
-
name: WAIT_FOR_EVENT_TOOL_NAME,
|
|
62
|
-
description: "Wait for the connected app to push an event via postEvent(name, payload) matching name (and, " +
|
|
63
|
-
"if match is given, every one of its keys strictly-equal — primitives only, not objects/arrays " +
|
|
64
|
-
"— in the event's payload). Checks already-retained events first, so an event that already " +
|
|
65
|
-
"fired before this call still resolves immediately; pass since (a cursor from a previous " +
|
|
66
|
-
"appduct_events/appduct_wait_for_event call) to skip events you've already handled and " +
|
|
67
|
-
"wait only for a new one — omitting it can return an old match instantly on every call. " +
|
|
68
|
-
"Requires a claimed session to already exist (use appduct_wait_for_session first if not). " +
|
|
69
|
-
"Resolves with { sessionId, alias, name, payload, ts, seq }, or rejects with tool_timeout after " +
|
|
70
|
-
"timeoutMs (default 120000ms, capped server-side at 1500000ms). A call still running after " +
|
|
71
|
-
"about two minutes moves to a Claude Code background task, so the result may arrive well after " +
|
|
72
|
-
"this call returns.",
|
|
73
|
-
inputSchema: {
|
|
74
|
-
type: "object",
|
|
75
|
-
properties: {
|
|
76
|
-
selector: { type: "string" },
|
|
77
|
-
name: { type: "string" },
|
|
78
|
-
match: { type: "object", additionalProperties: { type: ["string", "number", "boolean", "null"] } },
|
|
79
|
-
since: { type: "integer", minimum: 0 },
|
|
80
|
-
timeoutMs: { type: "number", exclusiveMinimum: 0 },
|
|
81
|
-
},
|
|
82
|
-
required: ["name"],
|
|
83
|
-
additionalProperties: false,
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
const asRecord = (value) => {
|
|
87
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
88
|
-
return {};
|
|
89
|
-
}
|
|
90
|
-
return value;
|
|
91
|
-
};
|
|
92
|
-
const asOptionalString = (value, field) => {
|
|
93
|
-
if (value === undefined) {
|
|
94
|
-
return undefined;
|
|
95
|
-
}
|
|
96
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
97
|
-
throw new McpBuiltinToolError("invalid_request", `"${field}" must be a non-empty string.`);
|
|
98
|
-
}
|
|
99
|
-
return value;
|
|
100
|
-
};
|
|
101
|
-
/** `since`/`limit` mirror the daemon's own `asEventsSinceParams` (`daemon/daemon.ts`), which
|
|
102
|
-
* requires integers — accepting e.g. `1.5` here would just round-trip to a `DaemonRpcError`
|
|
103
|
-
* instead of this tool's own clearer `invalid_request`. */
|
|
104
|
-
const asOptionalNonNegativeInteger = (value, field) => {
|
|
105
|
-
if (value === undefined) {
|
|
106
|
-
return undefined;
|
|
107
|
-
}
|
|
108
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
109
|
-
throw new McpBuiltinToolError("invalid_request", `"${field}" must be a non-negative integer.`);
|
|
110
|
-
}
|
|
111
|
-
return value;
|
|
112
|
-
};
|
|
113
|
-
const asOptionalPositiveInteger = (value, field) => {
|
|
114
|
-
if (value === undefined) {
|
|
115
|
-
return undefined;
|
|
116
|
-
}
|
|
117
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
118
|
-
throw new McpBuiltinToolError("invalid_request", `"${field}" must be a positive integer.`);
|
|
119
|
-
}
|
|
120
|
-
return value;
|
|
121
|
-
};
|
|
122
|
-
/** `timeoutMs` is not required to be an integer (unlike the daemon's `since`/`limit`) — it's never
|
|
123
|
-
* forwarded to the daemon verbatim, only used as this process's own `setTimeout` duration. */
|
|
124
|
-
const asOptionalPositiveNumber = (value, field) => {
|
|
125
|
-
if (value === undefined) {
|
|
126
|
-
return undefined;
|
|
127
|
-
}
|
|
128
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
129
|
-
throw new McpBuiltinToolError("invalid_request", `"${field}" must be a positive number.`);
|
|
130
|
-
}
|
|
131
|
-
return value;
|
|
132
|
-
};
|
|
133
|
-
const asOptionalEventKinds = (value) => {
|
|
134
|
-
if (value === undefined) {
|
|
135
|
-
return undefined;
|
|
136
|
-
}
|
|
137
|
-
if (!Array.isArray(value) || !value.every((kind) => typeof kind === "string" && KNOWN_EVENT_KINDS.has(kind))) {
|
|
138
|
-
throw new McpBuiltinToolError("invalid_request", '"kinds" must be an array of known event kinds.');
|
|
139
|
-
}
|
|
140
|
-
return value;
|
|
141
|
-
};
|
|
142
|
-
const isMatchPrimitive = (value) => {
|
|
143
|
-
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
144
|
-
};
|
|
145
|
-
/** `payloadShallowMatches` compares with `===`, so an object/array value here could never equal
|
|
146
|
-
* anything and would just make the wait time out with no explanation — reject it up front instead. */
|
|
147
|
-
const asOptionalMatch = (value) => {
|
|
148
|
-
if (value === undefined) {
|
|
149
|
-
return undefined;
|
|
150
|
-
}
|
|
151
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
152
|
-
throw new McpBuiltinToolError("invalid_request", '"match" must be a JSON object.');
|
|
153
|
-
}
|
|
154
|
-
const record = value;
|
|
155
|
-
for (const [key, entry] of Object.entries(record)) {
|
|
156
|
-
if (!isMatchPrimitive(entry)) {
|
|
157
|
-
throw new McpBuiltinToolError("invalid_request", `"match.${key}" must be a string, number, boolean, or null (objects/arrays can never match).`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return record;
|
|
161
|
-
};
|
|
162
|
-
export const handleEventsTool = async (rawArgs, deps) => {
|
|
163
|
-
const args = asRecord(rawArgs);
|
|
164
|
-
return deps.call(RPC_METHODS.eventsSince, {
|
|
165
|
-
selector: asOptionalString(args.selector, "selector"),
|
|
166
|
-
since: asOptionalNonNegativeInteger(args.since, "since"),
|
|
167
|
-
kinds: asOptionalEventKinds(args.kinds),
|
|
168
|
-
limit: asOptionalPositiveInteger(args.limit, "limit"),
|
|
169
|
-
});
|
|
170
|
-
};
|
|
171
|
-
const payloadShallowMatches = (payload, match) => {
|
|
172
|
-
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
const record = payload;
|
|
176
|
-
return Object.entries(match).every(([key, value]) => record[key] === value);
|
|
177
|
-
};
|
|
178
|
-
export const handleWaitForEventTool = async (rawArgs, deps) => {
|
|
179
|
-
const args = asRecord(rawArgs);
|
|
180
|
-
const selector = asOptionalString(args.selector, "selector");
|
|
181
|
-
const name = asOptionalString(args.name, "name");
|
|
182
|
-
if (!name) {
|
|
183
|
-
throw new McpBuiltinToolError("invalid_request", '"name" must be a non-empty string.');
|
|
184
|
-
}
|
|
185
|
-
const match = asOptionalMatch(args.match);
|
|
186
|
-
const since = asOptionalNonNegativeInteger(args.since, "since");
|
|
187
|
-
const timeoutMs = clampWaitForEventTimeoutMs(asOptionalPositiveNumber(args.timeoutMs, "timeoutMs"));
|
|
188
|
-
const stream = await openDaemonStream({ stateDir: deps.stateDir, spawn: deps.spawn });
|
|
189
|
-
try {
|
|
190
|
-
// Resolve the selector to one concrete session up front (same shape as
|
|
191
|
-
// `handleWaitForSessionTool`) so the buffer drain below and the live subscription target the
|
|
192
|
-
// exact same session — `events.subscribe`'s own `sessionSelector` has no "sole session" default,
|
|
193
|
-
// so leaving it unresolved here would silently widen the live half of the wait to every session.
|
|
194
|
-
// A `DaemonRpcError` (no_session/ambiguous_session/unknown_session) propagates unchanged —
|
|
195
|
-
// `toolErrorContentFromError` (mcp/server.ts) already maps it to its precise `data.type`.
|
|
196
|
-
const described = await stream.call(RPC_METHODS.sessionsDescribe, { selector });
|
|
197
|
-
const sessionId = described.sessionId;
|
|
198
|
-
const alias = described.alias;
|
|
199
|
-
const toResult = (event) => {
|
|
200
|
-
if (event.kind !== "app_event" || event.sessionId !== sessionId) {
|
|
201
|
-
return undefined;
|
|
202
|
-
}
|
|
203
|
-
const data = event.data;
|
|
204
|
-
if (data.name !== name) {
|
|
205
|
-
return undefined;
|
|
206
|
-
}
|
|
207
|
-
if (match && !payloadShallowMatches(data.payload, match)) {
|
|
208
|
-
return undefined;
|
|
209
|
-
}
|
|
210
|
-
return { sessionId, alias, name, payload: data.payload, ts: event.ts, seq: event.seq };
|
|
211
|
-
};
|
|
212
|
-
// The notification listener is registered *before* `events.subscribe` is even sent, and starts
|
|
213
|
-
// buffering into `earlyEvents` rather than resolving anything (`liveHandler` is still null) —
|
|
214
|
-
// see the module doc comment for why: the `events.since` response and a `notify()` for a
|
|
215
|
-
// brand-new event can arrive in the same socket chunk, and a listener added only after `await`ing
|
|
216
|
-
// that response has already missed a notification line from that same chunk.
|
|
217
|
-
let liveHandler = null;
|
|
218
|
-
const earlyEvents = [];
|
|
219
|
-
const unsubscribeNotification = stream.onNotification((payload) => {
|
|
220
|
-
const event = payload;
|
|
221
|
-
if (liveHandler) {
|
|
222
|
-
liveHandler(event);
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
earlyEvents.push(event);
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
let unsubscribeClose = () => { };
|
|
229
|
-
try {
|
|
230
|
-
await stream.call(RPC_METHODS.eventsSubscribe, { sessionSelector: sessionId, kinds: ["app_event"] });
|
|
231
|
-
const sinceResult = await stream.call(RPC_METHODS.eventsSince, {
|
|
232
|
-
selector: sessionId,
|
|
233
|
-
since,
|
|
234
|
-
kinds: ["app_event"],
|
|
235
|
-
});
|
|
236
|
-
// Merge the retained backlog with whatever arrived on the live channel while the two calls
|
|
237
|
-
// above were in flight, deduped by `seq` (the same event can legitimately show up in both:
|
|
238
|
-
// the daemon buffers before it fans out, so a late-drained event and an in-flight live one can
|
|
239
|
-
// be the same notification). Everything from here to `liveHandler = …` below is synchronous —
|
|
240
|
-
// no `await` — so nothing can arrive on the socket and be missed in the gap.
|
|
241
|
-
const seenSeqs = new Set();
|
|
242
|
-
const backlog = [];
|
|
243
|
-
for (const event of [...sinceResult.events, ...earlyEvents]) {
|
|
244
|
-
if (event.sessionId === sessionId && !seenSeqs.has(event.seq)) {
|
|
245
|
-
seenSeqs.add(event.seq);
|
|
246
|
-
backlog.push(event);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
backlog.sort((a, b) => a.seq - b.seq);
|
|
250
|
-
for (const event of backlog) {
|
|
251
|
-
const result = toResult(event);
|
|
252
|
-
if (result) {
|
|
253
|
-
return result;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
// Nothing matched yet: the cursor to resume live from is the last event actually considered
|
|
257
|
-
// (backlog's own scan already covers everything since.events + earlyEvents jointly saw), or —
|
|
258
|
-
// if nothing was retained/arrived at all — `events.since`'s own cursor, which (per
|
|
259
|
-
// `event-bus.ts`'s `since()`) already reflects the session's true high-water mark even when
|
|
260
|
-
// the `kinds` filter matched nothing.
|
|
261
|
-
const highestConsideredSeq = backlog.length > 0 ? backlog[backlog.length - 1].seq : sinceResult.cursor;
|
|
262
|
-
return await new Promise((resolve, reject) => {
|
|
263
|
-
let settled = false;
|
|
264
|
-
const settle = (fn) => {
|
|
265
|
-
if (settled) {
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
settled = true;
|
|
269
|
-
clearTimeout(timer);
|
|
270
|
-
if (progressTimer) {
|
|
271
|
-
clearInterval(progressTimer);
|
|
272
|
-
}
|
|
273
|
-
liveHandler = null;
|
|
274
|
-
fn();
|
|
275
|
-
};
|
|
276
|
-
const timer = setTimeout(() => {
|
|
277
|
-
settle(() => reject(new McpBuiltinToolError("tool_timeout", `Timed out after ${timeoutMs}ms waiting for event "${name}".`)));
|
|
278
|
-
}, timeoutMs);
|
|
279
|
-
const startedAt = Date.now();
|
|
280
|
-
const progressTimer = deps.progress
|
|
281
|
-
? setInterval(() => {
|
|
282
|
-
deps.progress.sendNotification({
|
|
283
|
-
method: "notifications/progress",
|
|
284
|
-
params: {
|
|
285
|
-
progressToken: deps.progress.token,
|
|
286
|
-
progress: Date.now() - startedAt,
|
|
287
|
-
total: timeoutMs,
|
|
288
|
-
message: `Still waiting for event "${name}"...`,
|
|
289
|
-
},
|
|
290
|
-
}).catch(() => {
|
|
291
|
-
// A failed progress notification must never abort the wait itself.
|
|
292
|
-
});
|
|
293
|
-
}, WAIT_FOR_EVENT_PROGRESS_INTERVAL_MS)
|
|
294
|
-
: undefined;
|
|
295
|
-
unsubscribeClose = stream.onClose(() => {
|
|
296
|
-
settle(() => reject(new McpBuiltinToolError("tool_execution_error", `The connection to the Appduct daemon closed while waiting for event "${name}".`)));
|
|
297
|
-
});
|
|
298
|
-
// From here on, every notification goes straight to this handler instead of `earlyEvents` —
|
|
299
|
-
// assigned synchronously (no `await` since the backlog scan above), so nothing is missed.
|
|
300
|
-
liveHandler = (event) => {
|
|
301
|
-
if (settled) {
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
if (event.sessionId === sessionId && event.seq <= highestConsideredSeq) {
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
const result = toResult(event);
|
|
308
|
-
if (result) {
|
|
309
|
-
settle(() => resolve(result));
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
finally {
|
|
315
|
-
unsubscribeNotification();
|
|
316
|
-
unsubscribeClose();
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
finally {
|
|
320
|
-
stream.close();
|
|
321
|
-
}
|
|
322
|
-
};
|
package/dist/mcp/server.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAa/E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAGpD,OAAO,EAKL,KAAK,OAAO,EACZ,KAAK,mBAAmB,EACzB,MAAM,kBAAkB,CAAC;AAwB1B,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AA8Q1D,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC;;;4EAGwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;+EAC2E;IAC3E,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;0CACsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB;;2EAEuE;IACvE,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,SAAS,sBAAsB,KAAG,OAAO,CAAC,eAAe,CAiY9F,CAAC"}
|