appduct 0.11.1 → 0.13.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/README.md +43 -38
- package/dist/bin.js +50 -36
- package/dist/cli/command-options.d.ts +7 -17
- package/dist/cli/result-types.d.ts +11 -8
- package/dist/cli/routes/daemon/index.js +12 -16
- package/dist/cli/routes/daemon/run.js +216 -46
- package/dist/cli/routes/daemon/start.js +18 -16
- package/dist/cli/routes/daemon/status.js +18 -16
- package/dist/cli/routes/daemon/stop.js +18 -16
- package/dist/cli/routes/doctor.js +12 -16
- package/dist/cli/routes/events/index.d.ts +9 -0
- package/dist/cli/routes/events/index.js +842 -0
- package/dist/cli/routes/events/shared-options.d.ts +12 -0
- package/dist/cli/routes/events/shared-options.js +102 -0
- package/dist/cli/routes/events/since.d.ts +6 -0
- package/dist/cli/routes/events/since.js +1977 -0
- package/dist/cli/routes/events/tail.d.ts +6 -0
- package/dist/cli/routes/{events.js → events/tail.js} +184 -162
- package/dist/cli/routes/init.js +14 -18
- package/dist/cli/routes/keygen.js +13 -16
- package/dist/cli/routes/mcp.js +193 -175
- package/dist/cli/routes/sessions/index.d.ts +7 -0
- package/dist/cli/routes/sessions/index.js +843 -0
- package/dist/cli/routes/sessions/link.d.ts +4 -0
- package/dist/cli/routes/{link.js → sessions/link.js} +19 -17
- package/dist/cli/routes/sessions/ls.d.ts +4 -0
- package/dist/cli/routes/{ls.js → sessions/ls.js} +19 -17
- package/dist/cli/routes/sessions/revoke.d.ts +4 -0
- package/dist/cli/routes/{revoke.js → sessions/revoke.js} +20 -18
- package/dist/cli/routes/tools/call.d.ts +4 -0
- package/dist/cli/routes/{invoke.js → tools/call.js} +21 -19
- package/dist/cli/routes/tools/describe.d.ts +6 -0
- package/dist/cli/routes/tools/describe.js +1837 -0
- package/dist/cli/routes/tools/index.d.ts +6 -0
- package/dist/cli/routes/tools/index.js +843 -0
- package/dist/cli/routes/tools/ls.d.ts +4 -0
- package/dist/cli/routes/{tools.js → tools/ls.js} +46 -73
- package/dist/cli/routes/tools/shared-options.d.ts +16 -0
- package/dist/cli/routes/tools/shared-options.js +122 -0
- package/dist/client/app-client.d.ts +96 -37
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.js +183 -139
- package/dist/commands/events.d.ts +31 -13
- package/dist/commands/invoke.d.ts +1 -1
- package/dist/commands/link.d.ts +1 -1
- package/dist/commands/ls.d.ts +1 -1
- package/dist/commands/revoke.d.ts +1 -1
- package/dist/commands/tools.d.ts +12 -13
- package/dist/daemon/config.d.ts +5 -2
- package/dist/daemon/daemon.d.ts +3 -0
- package/dist/daemon/event-bus.d.ts +36 -4
- package/dist/daemon/events-log.d.ts +31 -0
- package/dist/daemon/memory-append-only-file.d.ts +17 -0
- package/dist/daemon/node-append-only-file.d.ts +9 -0
- package/dist/daemon/rpc-errors.d.ts +1 -1
- package/dist/daemon/state-dir.d.ts +2 -0
- package/dist/errors.d.ts +1 -1
- package/dist/events/index.d.ts +60 -0
- package/dist/index.js +62 -39
- package/dist/link.d.ts +1 -1
- package/dist/mcp/app-tools.d.ts +3 -2
- package/dist/mcp/connect-tool.d.ts +1 -1
- package/dist/mcp/events-tool.d.ts +28 -34
- package/dist/native-scheme.d.ts +1 -1
- package/dist/output.d.ts +29 -6
- package/dist/rpc/client.d.ts +1 -1
- package/dist/scheme.d.ts +4 -4
- package/package.json +2 -2
- package/dist/cli/routes/events.d.ts +0 -3
- package/dist/cli/routes/invoke.d.ts +0 -3
- package/dist/cli/routes/link.d.ts +0 -3
- package/dist/cli/routes/ls.d.ts +0 -3
- package/dist/cli/routes/revoke.d.ts +0 -3
- package/dist/cli/routes/tools.d.ts +0 -3
package/dist/cli/routes/mcp.js
CHANGED
|
@@ -12,6 +12,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
12
12
|
"eventBufferSize",
|
|
13
13
|
"auditRetentionDays",
|
|
14
14
|
"daemonLogMaxBytes",
|
|
15
|
+
"eventsLogMaxBytes",
|
|
15
16
|
"policy",
|
|
16
17
|
"advertisedIp",
|
|
17
18
|
"scheme",
|
|
@@ -71,6 +72,7 @@ var defaultConfig = (paths) => {
|
|
|
71
72
|
eventBufferSize: 256,
|
|
72
73
|
auditRetentionDays: DEFAULT_AUDIT_RETENTION_DAYS,
|
|
73
74
|
daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
|
|
75
|
+
eventsLogMaxBytes: 10 * 1024 * 1024,
|
|
74
76
|
restartDaemonOnVersionMismatch: false,
|
|
75
77
|
policy: {
|
|
76
78
|
default: "allow",
|
|
@@ -133,6 +135,9 @@ var loadConfig = async (paths, options = {}) => {
|
|
|
133
135
|
if (parsed.daemonLogMaxBytes !== void 0) {
|
|
134
136
|
config.daemonLogMaxBytes = requirePositiveInteger(parsed.daemonLogMaxBytes, "daemonLogMaxBytes");
|
|
135
137
|
}
|
|
138
|
+
if (parsed.eventsLogMaxBytes !== void 0) {
|
|
139
|
+
config.eventsLogMaxBytes = requirePositiveInteger(parsed.eventsLogMaxBytes, "eventsLogMaxBytes");
|
|
140
|
+
}
|
|
136
141
|
if (parsed.restartDaemonOnVersionMismatch !== void 0) {
|
|
137
142
|
config.restartDaemonOnVersionMismatch = requireBoolean(
|
|
138
143
|
parsed.restartDaemonOnVersionMismatch,
|
|
@@ -178,6 +183,7 @@ var getStateDirPaths = (stateDir) => {
|
|
|
178
183
|
socketPath: join(stateDir, "daemon.sock"),
|
|
179
184
|
pidFilePath: join(stateDir, "daemon.pid"),
|
|
180
185
|
logFilePath: join(stateDir, "daemon.log"),
|
|
186
|
+
eventsLogPath: join(stateDir, "events.log"),
|
|
181
187
|
keyPath: join(stateDir, "key.pem"),
|
|
182
188
|
configPath: join(stateDir, "config.json"),
|
|
183
189
|
auditDir: join(stateDir, "audit"),
|
|
@@ -202,7 +208,7 @@ import {
|
|
|
202
208
|
McpError,
|
|
203
209
|
ReadResourceRequestSchema
|
|
204
210
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
205
|
-
import { RPC_METHODS as
|
|
211
|
+
import { RPC_METHODS as RPC_METHODS7 } from "@appduct/shared";
|
|
206
212
|
|
|
207
213
|
// src/daemon/calls.ts
|
|
208
214
|
import {
|
|
@@ -2649,44 +2655,147 @@ var resolveAppTool = async (call, selector, name) => {
|
|
|
2649
2655
|
};
|
|
2650
2656
|
|
|
2651
2657
|
// src/mcp/events-tool.ts
|
|
2658
|
+
import {
|
|
2659
|
+
RPC_METHODS as RPC_METHODS6,
|
|
2660
|
+
toAppEvent as toAppEvent2
|
|
2661
|
+
} from "@appduct/shared";
|
|
2662
|
+
|
|
2663
|
+
// src/events/index.ts
|
|
2652
2664
|
import {
|
|
2653
2665
|
RPC_METHODS as RPC_METHODS5,
|
|
2654
|
-
|
|
2666
|
+
toAppEvent
|
|
2655
2667
|
} from "@appduct/shared";
|
|
2668
|
+
var WaitForAppEventTimeoutError = class extends Error {
|
|
2669
|
+
constructor(eventName, timeoutMs) {
|
|
2670
|
+
super(`Timed out after ${timeoutMs}ms waiting for event "${eventName}".`);
|
|
2671
|
+
this.eventName = eventName;
|
|
2672
|
+
this.timeoutMs = timeoutMs;
|
|
2673
|
+
this.name = "WaitForAppEventTimeoutError";
|
|
2674
|
+
}
|
|
2675
|
+
};
|
|
2676
|
+
var WaitForAppEventConnectionClosedError = class extends Error {
|
|
2677
|
+
constructor(eventName) {
|
|
2678
|
+
super(`The connection to the Appduct daemon closed while waiting for event "${eventName}".`);
|
|
2679
|
+
this.eventName = eventName;
|
|
2680
|
+
this.name = "WaitForAppEventConnectionClosedError";
|
|
2681
|
+
}
|
|
2682
|
+
};
|
|
2683
|
+
var waitForAppEvent = async (stream, options) => {
|
|
2684
|
+
const { sessionId, name, since, timeoutMs, payloadMaxBytes } = options;
|
|
2685
|
+
let liveHandler = null;
|
|
2686
|
+
const earlyEvents = [];
|
|
2687
|
+
const unsubscribeNotification = stream.onNotification((payload) => {
|
|
2688
|
+
const event = payload;
|
|
2689
|
+
if (liveHandler) {
|
|
2690
|
+
liveHandler(event);
|
|
2691
|
+
} else {
|
|
2692
|
+
earlyEvents.push(event);
|
|
2693
|
+
}
|
|
2694
|
+
});
|
|
2695
|
+
let unsubscribeClose = () => {
|
|
2696
|
+
};
|
|
2697
|
+
try {
|
|
2698
|
+
await stream.call(RPC_METHODS5.eventsSubscribe, {
|
|
2699
|
+
sessionSelector: sessionId,
|
|
2700
|
+
kinds: ["app_event"],
|
|
2701
|
+
name,
|
|
2702
|
+
payloadMaxBytes
|
|
2703
|
+
});
|
|
2704
|
+
const sinceResult = await stream.call(RPC_METHODS5.eventsSince, {
|
|
2705
|
+
selector: sessionId,
|
|
2706
|
+
since,
|
|
2707
|
+
name,
|
|
2708
|
+
payloadMaxBytes
|
|
2709
|
+
});
|
|
2710
|
+
const seenSeqs = /* @__PURE__ */ new Set();
|
|
2711
|
+
const backlog = [];
|
|
2712
|
+
for (const event of [...sinceResult.events, ...earlyEvents]) {
|
|
2713
|
+
if (event.sessionId === sessionId && !seenSeqs.has(event.seq)) {
|
|
2714
|
+
seenSeqs.add(event.seq);
|
|
2715
|
+
backlog.push(event);
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
backlog.sort((a, b) => a.seq - b.seq);
|
|
2719
|
+
for (const event of backlog) {
|
|
2720
|
+
const appEvent = toAppEvent(event, sessionId);
|
|
2721
|
+
if (appEvent) {
|
|
2722
|
+
return { event: appEvent, dropped: sinceResult.dropped };
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
const highestConsideredSeq = backlog.length > 0 ? backlog[backlog.length - 1].seq : sinceResult.cursor;
|
|
2726
|
+
return await new Promise((resolve2, reject) => {
|
|
2727
|
+
let settled = false;
|
|
2728
|
+
const settle = (fn) => {
|
|
2729
|
+
if (settled) {
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
settled = true;
|
|
2733
|
+
clearTimeout(timer);
|
|
2734
|
+
liveHandler = null;
|
|
2735
|
+
fn();
|
|
2736
|
+
};
|
|
2737
|
+
const timer = setTimeout(() => {
|
|
2738
|
+
settle(() => reject(new WaitForAppEventTimeoutError(name, timeoutMs)));
|
|
2739
|
+
}, timeoutMs);
|
|
2740
|
+
unsubscribeClose = stream.onClose(() => {
|
|
2741
|
+
settle(() => reject(new WaitForAppEventConnectionClosedError(name)));
|
|
2742
|
+
});
|
|
2743
|
+
liveHandler = (event) => {
|
|
2744
|
+
if (settled) {
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2747
|
+
if (event.sessionId === sessionId && event.seq <= highestConsideredSeq) {
|
|
2748
|
+
return;
|
|
2749
|
+
}
|
|
2750
|
+
const appEvent = toAppEvent(event, sessionId);
|
|
2751
|
+
if (appEvent) {
|
|
2752
|
+
settle(() => resolve2({ event: appEvent, dropped: sinceResult.dropped }));
|
|
2753
|
+
}
|
|
2754
|
+
};
|
|
2755
|
+
});
|
|
2756
|
+
} finally {
|
|
2757
|
+
unsubscribeNotification();
|
|
2758
|
+
unsubscribeClose();
|
|
2759
|
+
}
|
|
2760
|
+
};
|
|
2761
|
+
|
|
2762
|
+
// src/mcp/events-tool.ts
|
|
2656
2763
|
var EVENTS_TOOL_NAME = "appduct_events";
|
|
2657
2764
|
var WAIT_FOR_EVENT_TOOL_NAME = "appduct_wait_for_event";
|
|
2765
|
+
var DEFAULT_EVENTS_LIMIT = 50;
|
|
2766
|
+
var DEFAULT_EVENTS_PAYLOAD_MAX_BYTES = 4096;
|
|
2658
2767
|
var DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MS = 12e4;
|
|
2659
2768
|
var MAX_WAIT_FOR_EVENT_TIMEOUT_MS = 25 * 60 * 1e3;
|
|
2660
2769
|
var WAIT_FOR_EVENT_PROGRESS_INTERVAL_MS = 6e4;
|
|
2661
|
-
var KNOWN_EVENT_KINDS = new Set(EVENT_KINDS);
|
|
2662
2770
|
var clampWaitForEventTimeoutMs = (requestedMs) => {
|
|
2663
2771
|
return Math.min(requestedMs ?? DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MS, MAX_WAIT_FOR_EVENT_TIMEOUT_MS);
|
|
2664
2772
|
};
|
|
2665
2773
|
var EVENTS_TOOL_DESCRIPTOR = {
|
|
2666
2774
|
name: EVENTS_TOOL_NAME,
|
|
2667
|
-
description:
|
|
2775
|
+
description: `Drain the events the app posted with postEvent(name, payload) that the daemon retained for a session since a cursor, for checking what the app reported after calling a tool or triggering app behavior. With no selector, targets the sole active/suspended session. name filters to events whose name matches a whole-name, case-sensitive glob (* matches any run of characters; a pattern without * is an exact name), e.g. "cart.*". Returns { events, cursor, dropped, remaining } with each event as { name, payload, ts, seq, sessionId, alias } or, once a payload's JSON exceeds payloadMaxBytes (default 4096), as { name, payloadPreview, truncated: true, payloadBytes, ts, seq, sessionId, alias } instead \u2014 check truncated before reading payload. pass cursor back as since on the next call to avoid re-reading events you've already seen; dropped counts app events after since that were evicted before this call could return them (0 once nothing has fallen off), and remaining counts events still matching this query after the returned page (0 on the last page). limit (default 50) keeps the OLDEST events in the window and advances cursor only past what was actually returned, so repeated calls page forward through everything retained rather than skipping ahead.`,
|
|
2668
2776
|
inputSchema: {
|
|
2669
2777
|
type: "object",
|
|
2670
2778
|
properties: {
|
|
2671
2779
|
selector: { type: "string" },
|
|
2672
2780
|
since: { type: "integer", minimum: 0 },
|
|
2673
|
-
|
|
2674
|
-
|
|
2781
|
+
limit: { type: "integer", exclusiveMinimum: 0 },
|
|
2782
|
+
name: { type: "string" },
|
|
2783
|
+
payloadMaxBytes: { type: "integer", exclusiveMinimum: 0 }
|
|
2675
2784
|
},
|
|
2676
2785
|
additionalProperties: false
|
|
2677
2786
|
}
|
|
2678
2787
|
};
|
|
2679
2788
|
var WAIT_FOR_EVENT_TOOL_DESCRIPTOR = {
|
|
2680
2789
|
name: WAIT_FOR_EVENT_TOOL_NAME,
|
|
2681
|
-
description:
|
|
2790
|
+
description: `Wait for the connected app to push an event via postEvent(name, payload) whose name matches name, a whole-name, case-sensitive glob (* matches any run of characters; a pattern without * is an exact name, e.g. "cart.*", or "*" for the next event of any name). Checks already-retained events first, so an event that already fired before this call still resolves immediately; pass since (a cursor from a previous appduct_events/appduct_wait_for_event call) to skip events you've already handled and wait only for a new one \u2014 omitting it can return an old match instantly on every call. Requires a claimed session to already exist (use appduct_wait_for_session first if not). Resolves with { sessionId, alias, name, payload, ts, seq, dropped } or, once a payload's JSON exceeds payloadMaxBytes (default 4096), with { payloadPreview, truncated: true, payloadBytes } in place of payload \u2014 check truncated before reading payload; dropped counts app events after since that were evicted before this call could return them (0 once nothing has fallen off). Rejects with tool_timeout after timeoutMs (default 120000ms, capped server-side at 1500000ms). A call still running after about two minutes moves to a Claude Code background task, so the result may arrive well after this call returns.`,
|
|
2682
2791
|
inputSchema: {
|
|
2683
2792
|
type: "object",
|
|
2684
2793
|
properties: {
|
|
2685
2794
|
selector: { type: "string" },
|
|
2686
2795
|
name: { type: "string" },
|
|
2687
|
-
match: { type: "object", additionalProperties: { type: ["string", "number", "boolean", "null"] } },
|
|
2688
2796
|
since: { type: "integer", minimum: 0 },
|
|
2689
|
-
timeoutMs: { type: "number", exclusiveMinimum: 0 }
|
|
2797
|
+
timeoutMs: { type: "number", exclusiveMinimum: 0 },
|
|
2798
|
+
payloadMaxBytes: { type: "integer", exclusiveMinimum: 0 }
|
|
2690
2799
|
},
|
|
2691
2800
|
required: ["name"],
|
|
2692
2801
|
additionalProperties: false
|
|
@@ -2734,178 +2843,91 @@ var asOptionalPositiveNumber2 = (value, field) => {
|
|
|
2734
2843
|
}
|
|
2735
2844
|
return value;
|
|
2736
2845
|
};
|
|
2737
|
-
var
|
|
2738
|
-
if (
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2846
|
+
var rejectKinds = (args) => {
|
|
2847
|
+
if (args.kinds !== void 0) {
|
|
2848
|
+
throw new McpBuiltinToolError(
|
|
2849
|
+
"invalid_request",
|
|
2850
|
+
'"kinds" is not supported: these tools return only the events the app posted with postEvent.'
|
|
2851
|
+
);
|
|
2743
2852
|
}
|
|
2744
|
-
return value;
|
|
2745
2853
|
};
|
|
2746
|
-
var
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
}
|
|
2753
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2754
|
-
throw new McpBuiltinToolError("invalid_request", '"match" must be a JSON object.');
|
|
2755
|
-
}
|
|
2756
|
-
const record = value;
|
|
2757
|
-
for (const [key, entry] of Object.entries(record)) {
|
|
2758
|
-
if (!isMatchPrimitive(entry)) {
|
|
2759
|
-
throw new McpBuiltinToolError(
|
|
2760
|
-
"invalid_request",
|
|
2761
|
-
`"match.${key}" must be a string, number, boolean, or null (objects/arrays can never match).`
|
|
2762
|
-
);
|
|
2763
|
-
}
|
|
2854
|
+
var rejectMatch = (args) => {
|
|
2855
|
+
if (args.match !== void 0) {
|
|
2856
|
+
throw new McpBuiltinToolError(
|
|
2857
|
+
"invalid_request",
|
|
2858
|
+
'"match" is not supported: "name" is a glob, and a payload predicate is not offered on any surface \u2014 loop on appduct_wait_for_event with "since" instead.'
|
|
2859
|
+
);
|
|
2764
2860
|
}
|
|
2765
|
-
return record;
|
|
2766
2861
|
};
|
|
2767
2862
|
var handleEventsTool = async (rawArgs, deps) => {
|
|
2768
2863
|
const args = asRecord3(rawArgs);
|
|
2769
|
-
|
|
2864
|
+
rejectKinds(args);
|
|
2865
|
+
const result = await deps.call(RPC_METHODS6.eventsSince, {
|
|
2770
2866
|
selector: asOptionalString2(args.selector, "selector"),
|
|
2771
2867
|
since: asOptionalNonNegativeInteger(args.since, "since"),
|
|
2772
|
-
|
|
2773
|
-
|
|
2868
|
+
limit: asOptionalPositiveInteger(args.limit, "limit") ?? DEFAULT_EVENTS_LIMIT,
|
|
2869
|
+
name: asOptionalString2(args.name, "name"),
|
|
2870
|
+
// Issue #113: this tool always caps a payload, so a caller reading the result straight into
|
|
2871
|
+
// its context can't be blown out by one gigantic app-pushed payload; `payloadMaxBytes` lets a
|
|
2872
|
+
// caller raise or lower that default.
|
|
2873
|
+
payloadMaxBytes: asOptionalPositiveInteger(args.payloadMaxBytes, "payloadMaxBytes") ?? DEFAULT_EVENTS_PAYLOAD_MAX_BYTES
|
|
2774
2874
|
});
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
return Object.entries(match).every(([key, value]) => record[key] === value);
|
|
2875
|
+
return {
|
|
2876
|
+
events: result.events.map((event) => event.sessionId === void 0 ? void 0 : toAppEvent2(event, event.sessionId)).filter((event) => event !== void 0),
|
|
2877
|
+
cursor: result.cursor,
|
|
2878
|
+
dropped: result.dropped,
|
|
2879
|
+
remaining: result.remaining
|
|
2880
|
+
};
|
|
2782
2881
|
};
|
|
2783
2882
|
var handleWaitForEventTool = async (rawArgs, deps) => {
|
|
2784
2883
|
const args = asRecord3(rawArgs);
|
|
2884
|
+
rejectKinds(args);
|
|
2885
|
+
rejectMatch(args);
|
|
2785
2886
|
const selector = asOptionalString2(args.selector, "selector");
|
|
2786
2887
|
const name = asOptionalString2(args.name, "name");
|
|
2787
2888
|
if (!name) {
|
|
2788
2889
|
throw new McpBuiltinToolError("invalid_request", '"name" must be a non-empty string.');
|
|
2789
2890
|
}
|
|
2790
|
-
const match = asOptionalMatch(args.match);
|
|
2791
2891
|
const since = asOptionalNonNegativeInteger(args.since, "since");
|
|
2792
2892
|
const timeoutMs = clampWaitForEventTimeoutMs(asOptionalPositiveNumber2(args.timeoutMs, "timeoutMs"));
|
|
2893
|
+
const payloadMaxBytes = asOptionalPositiveInteger(args.payloadMaxBytes, "payloadMaxBytes") ?? DEFAULT_EVENTS_PAYLOAD_MAX_BYTES;
|
|
2793
2894
|
const stream = await openDaemonStream({ stateDir: deps.stateDir, spawn: deps.spawn });
|
|
2794
2895
|
try {
|
|
2795
|
-
const described = await stream.call(
|
|
2796
|
-
const
|
|
2797
|
-
const
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
return { sessionId, alias, name, payload: data.payload, ts: event.ts, seq: event.seq };
|
|
2810
|
-
};
|
|
2811
|
-
let liveHandler = null;
|
|
2812
|
-
const earlyEvents = [];
|
|
2813
|
-
const unsubscribeNotification = stream.onNotification((payload) => {
|
|
2814
|
-
const event = payload;
|
|
2815
|
-
if (liveHandler) {
|
|
2816
|
-
liveHandler(event);
|
|
2817
|
-
} else {
|
|
2818
|
-
earlyEvents.push(event);
|
|
2819
|
-
}
|
|
2820
|
-
});
|
|
2821
|
-
let unsubscribeClose = () => {
|
|
2822
|
-
};
|
|
2896
|
+
const described = await stream.call(RPC_METHODS6.sessionsDescribe, { selector });
|
|
2897
|
+
const startedAt = Date.now();
|
|
2898
|
+
const progressTimer = deps.progress ? setInterval(() => {
|
|
2899
|
+
deps.progress.sendNotification({
|
|
2900
|
+
method: "notifications/progress",
|
|
2901
|
+
params: {
|
|
2902
|
+
progressToken: deps.progress.token,
|
|
2903
|
+
progress: Date.now() - startedAt,
|
|
2904
|
+
total: timeoutMs,
|
|
2905
|
+
message: `Still waiting for event "${name}"...`
|
|
2906
|
+
}
|
|
2907
|
+
}).catch(() => {
|
|
2908
|
+
});
|
|
2909
|
+
}, WAIT_FOR_EVENT_PROGRESS_INTERVAL_MS) : void 0;
|
|
2823
2910
|
try {
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2911
|
+
const { event, dropped } = await waitForAppEvent(stream, {
|
|
2912
|
+
sessionId: described.sessionId,
|
|
2913
|
+
name,
|
|
2827
2914
|
since,
|
|
2828
|
-
|
|
2915
|
+
timeoutMs,
|
|
2916
|
+
payloadMaxBytes
|
|
2829
2917
|
});
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
seenSeqs.add(event.seq);
|
|
2835
|
-
backlog.push(event);
|
|
2836
|
-
}
|
|
2918
|
+
return { ...event, dropped };
|
|
2919
|
+
} catch (error) {
|
|
2920
|
+
if (error instanceof WaitForAppEventTimeoutError) {
|
|
2921
|
+
throw new McpBuiltinToolError("tool_timeout", error.message);
|
|
2837
2922
|
}
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
const result = toResult(event);
|
|
2841
|
-
if (result) {
|
|
2842
|
-
return result;
|
|
2843
|
-
}
|
|
2923
|
+
if (error instanceof WaitForAppEventConnectionClosedError) {
|
|
2924
|
+
throw new McpBuiltinToolError("tool_execution_error", error.message);
|
|
2844
2925
|
}
|
|
2845
|
-
|
|
2846
|
-
return await new Promise((resolve2, reject) => {
|
|
2847
|
-
let settled = false;
|
|
2848
|
-
const settle = (fn) => {
|
|
2849
|
-
if (settled) {
|
|
2850
|
-
return;
|
|
2851
|
-
}
|
|
2852
|
-
settled = true;
|
|
2853
|
-
clearTimeout(timer);
|
|
2854
|
-
if (progressTimer) {
|
|
2855
|
-
clearInterval(progressTimer);
|
|
2856
|
-
}
|
|
2857
|
-
liveHandler = null;
|
|
2858
|
-
fn();
|
|
2859
|
-
};
|
|
2860
|
-
const timer = setTimeout(() => {
|
|
2861
|
-
settle(
|
|
2862
|
-
() => reject(
|
|
2863
|
-
new McpBuiltinToolError(
|
|
2864
|
-
"tool_timeout",
|
|
2865
|
-
`Timed out after ${timeoutMs}ms waiting for event "${name}".`
|
|
2866
|
-
)
|
|
2867
|
-
)
|
|
2868
|
-
);
|
|
2869
|
-
}, timeoutMs);
|
|
2870
|
-
const startedAt = Date.now();
|
|
2871
|
-
const progressTimer = deps.progress ? setInterval(() => {
|
|
2872
|
-
deps.progress.sendNotification({
|
|
2873
|
-
method: "notifications/progress",
|
|
2874
|
-
params: {
|
|
2875
|
-
progressToken: deps.progress.token,
|
|
2876
|
-
progress: Date.now() - startedAt,
|
|
2877
|
-
total: timeoutMs,
|
|
2878
|
-
message: `Still waiting for event "${name}"...`
|
|
2879
|
-
}
|
|
2880
|
-
}).catch(() => {
|
|
2881
|
-
});
|
|
2882
|
-
}, WAIT_FOR_EVENT_PROGRESS_INTERVAL_MS) : void 0;
|
|
2883
|
-
unsubscribeClose = stream.onClose(() => {
|
|
2884
|
-
settle(
|
|
2885
|
-
() => reject(
|
|
2886
|
-
new McpBuiltinToolError(
|
|
2887
|
-
"tool_execution_error",
|
|
2888
|
-
`The connection to the Appduct daemon closed while waiting for event "${name}".`
|
|
2889
|
-
)
|
|
2890
|
-
)
|
|
2891
|
-
);
|
|
2892
|
-
});
|
|
2893
|
-
liveHandler = (event) => {
|
|
2894
|
-
if (settled) {
|
|
2895
|
-
return;
|
|
2896
|
-
}
|
|
2897
|
-
if (event.sessionId === sessionId && event.seq <= highestConsideredSeq) {
|
|
2898
|
-
return;
|
|
2899
|
-
}
|
|
2900
|
-
const result = toResult(event);
|
|
2901
|
-
if (result) {
|
|
2902
|
-
settle(() => resolve2(result));
|
|
2903
|
-
}
|
|
2904
|
-
};
|
|
2905
|
-
});
|
|
2926
|
+
throw error;
|
|
2906
2927
|
} finally {
|
|
2907
|
-
|
|
2908
|
-
|
|
2928
|
+
if (progressTimer) {
|
|
2929
|
+
clearInterval(progressTimer);
|
|
2930
|
+
}
|
|
2909
2931
|
}
|
|
2910
2932
|
} finally {
|
|
2911
2933
|
stream.close();
|
|
@@ -3045,7 +3067,7 @@ var createMcpServer = async (options) => {
|
|
|
3045
3067
|
}
|
|
3046
3068
|
if (progressToken === void 0) {
|
|
3047
3069
|
const result = await stream.call(
|
|
3048
|
-
|
|
3070
|
+
RPC_METHODS7.toolsCall,
|
|
3049
3071
|
{
|
|
3050
3072
|
selector: tool.sessionId,
|
|
3051
3073
|
name: tool.descriptor.name,
|
|
@@ -3060,7 +3082,7 @@ var createMcpServer = async (options) => {
|
|
|
3060
3082
|
}
|
|
3061
3083
|
const progressStream = await openStream({ stateDir: options.stateDir, spawn: options.spawn });
|
|
3062
3084
|
try {
|
|
3063
|
-
await progressStream.call(
|
|
3085
|
+
await progressStream.call(RPC_METHODS7.eventsSubscribe, {
|
|
3064
3086
|
sessionSelector: tool.sessionId,
|
|
3065
3087
|
kinds: ["tool_call_started", "tool_call_progress"]
|
|
3066
3088
|
});
|
|
@@ -3070,7 +3092,7 @@ var createMcpServer = async (options) => {
|
|
|
3070
3092
|
if (callId === void 0 || !cancelRequested) {
|
|
3071
3093
|
return;
|
|
3072
3094
|
}
|
|
3073
|
-
progressStream.call(
|
|
3095
|
+
progressStream.call(RPC_METHODS7.toolsCancel, { selector: tool.sessionId, callId, reason: "mcp_client_cancelled" }).catch((error) => {
|
|
3074
3096
|
console.error("appduct mcp: failed to cancel an app tool call:", error);
|
|
3075
3097
|
});
|
|
3076
3098
|
};
|
|
@@ -3103,7 +3125,7 @@ var createMcpServer = async (options) => {
|
|
|
3103
3125
|
});
|
|
3104
3126
|
try {
|
|
3105
3127
|
const result = await progressStream.call(
|
|
3106
|
-
|
|
3128
|
+
RPC_METHODS7.toolsCall,
|
|
3107
3129
|
{
|
|
3108
3130
|
selector: tool.sessionId,
|
|
3109
3131
|
name: tool.descriptor.name,
|
|
@@ -3215,7 +3237,7 @@ var createMcpServer = async (options) => {
|
|
|
3215
3237
|
if (request.params.uri !== SESSIONS_RESOURCE_URI) {
|
|
3216
3238
|
throw new Error(`Unknown resource "${request.params.uri}".`);
|
|
3217
3239
|
}
|
|
3218
|
-
const sessions = await stream.call(
|
|
3240
|
+
const sessions = await stream.call(RPC_METHODS7.sessionsList);
|
|
3219
3241
|
return {
|
|
3220
3242
|
contents: [{ uri: SESSIONS_RESOURCE_URI, mimeType: "application/json", text: JSON.stringify(sessions) }]
|
|
3221
3243
|
};
|
|
@@ -3387,9 +3409,6 @@ var renderLsData = (colors, data, now) => {
|
|
|
3387
3409
|
...rows.map((row) => ` ${row.map((cell, index) => cell.padEnd(widths[index])).join(" ")}`)
|
|
3388
3410
|
];
|
|
3389
3411
|
};
|
|
3390
|
-
var isToolsListing = (data) => {
|
|
3391
|
-
return typeof data === "object" && data !== null && Array.isArray(data.tools);
|
|
3392
|
-
};
|
|
3393
3412
|
var isToolGroupsListing = (data) => {
|
|
3394
3413
|
return typeof data === "object" && data !== null && Array.isArray(data.groups) && !Array.isArray(data.tools);
|
|
3395
3414
|
};
|
|
@@ -3407,7 +3426,7 @@ var renderEmptyToolsLine = (data) => {
|
|
|
3407
3426
|
}
|
|
3408
3427
|
if (data.group !== void 0) {
|
|
3409
3428
|
const match = data.filter === void 0 ? "" : ` match ${JSON.stringify(data.filter)}`;
|
|
3410
|
-
return ` No tools in group ${JSON.stringify(data.group)}${match}. Run \`appduct tools --groups\` to see the session's groups.`;
|
|
3429
|
+
return ` No tools in group ${JSON.stringify(data.group)}${match}. Run \`appduct tools ls --groups\` to see the session's groups.`;
|
|
3411
3430
|
}
|
|
3412
3431
|
return data.filter === void 0 ? " No tools registered." : ` No tools match ${JSON.stringify(data.filter)}.`;
|
|
3413
3432
|
};
|
|
@@ -3489,7 +3508,7 @@ var renderToolSummaryTable = (colors, data) => {
|
|
|
3489
3508
|
...grouped ? renderGroupedToolLines(colors, data.tools) : data.tools.flatMap((tool) => renderToolSummaryLines(tool, " ")),
|
|
3490
3509
|
...renderTruncationLine(data),
|
|
3491
3510
|
"",
|
|
3492
|
-
"Run `appduct tools <name>` for a tool's full schema."
|
|
3511
|
+
"Run `appduct tools describe <name>` for a tool's full schema."
|
|
3493
3512
|
];
|
|
3494
3513
|
};
|
|
3495
3514
|
var renderToolDetail = (colors, tool, flags) => {
|
|
@@ -3541,16 +3560,13 @@ var renderToolGroups = (colors, data) => {
|
|
|
3541
3560
|
colors.green("Groups"),
|
|
3542
3561
|
...data.groups.map((entry) => ` ${label(entry).padEnd(width)} ${String(entry.total).padStart(countWidth)}`),
|
|
3543
3562
|
"",
|
|
3544
|
-
`${data.total} tool${data.total === 1 ? "" : "s"} in total. ` + (hasAnyGroup(data.groups) ? "Run `appduct tools --group <name>` to list one group's tools." : "No tool declares a group.")
|
|
3563
|
+
`${data.total} tool${data.total === 1 ? "" : "s"} in total. ` + (hasAnyGroup(data.groups) ? "Run `appduct tools ls --group <name>` to list one group's tools." : "No tool declares a group.")
|
|
3545
3564
|
];
|
|
3546
3565
|
};
|
|
3547
|
-
var
|
|
3566
|
+
var renderToolsListData = (colors, data, flags, full) => {
|
|
3548
3567
|
if (isToolGroupsListing(data)) {
|
|
3549
3568
|
return renderToolGroups(colors, data);
|
|
3550
3569
|
}
|
|
3551
|
-
if (!isToolsListing(data)) {
|
|
3552
|
-
return renderToolDetail(colors, data, flags);
|
|
3553
|
-
}
|
|
3554
3570
|
return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
|
|
3555
3571
|
};
|
|
3556
3572
|
var renderInvokeData = (colors, data, flags) => {
|
|
@@ -3736,15 +3752,17 @@ var renderSuccessData = (colors, command, data, options) => {
|
|
|
3736
3752
|
return renderInitData(colors, data, flags);
|
|
3737
3753
|
case "keygen":
|
|
3738
3754
|
return renderKeygenData(colors, data, flags);
|
|
3739
|
-
case "link":
|
|
3755
|
+
case "sessions link":
|
|
3740
3756
|
return renderLinkData(colors, data, flags, options.qr);
|
|
3741
|
-
case "ls":
|
|
3757
|
+
case "sessions ls":
|
|
3742
3758
|
return renderLsData(colors, data, options.now ?? /* @__PURE__ */ new Date());
|
|
3743
|
-
case "tools":
|
|
3744
|
-
return
|
|
3745
|
-
case "
|
|
3759
|
+
case "tools ls":
|
|
3760
|
+
return renderToolsListData(colors, data, flags, options.full);
|
|
3761
|
+
case "tools describe":
|
|
3762
|
+
return renderToolDetail(colors, data, flags);
|
|
3763
|
+
case "tools call":
|
|
3746
3764
|
return renderInvokeData(colors, data, flags);
|
|
3747
|
-
case "revoke":
|
|
3765
|
+
case "sessions revoke":
|
|
3748
3766
|
return renderRevokeData(colors, data);
|
|
3749
3767
|
case "daemon run":
|
|
3750
3768
|
return renderDaemonRunData(colors, data, flags);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route for `appduct sessions` — itself a router over the session verbs, so `sessions ls` loads
|
|
3
|
+
* only `ls.ts` (issue #96). cac matches only the noun (`cli/create-cli.ts`), so the verb arrives
|
|
4
|
+
* here as the first positional rather than as a cac command of its own — the same shape
|
|
5
|
+
* `routes/daemon/index.ts` already uses for `daemon status`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const route: import("../../router.js").Route;
|