appduct 0.12.0 → 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 +41 -36
- package/dist/bin.js +44 -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 +136 -28
- package/dist/cli/routes/daemon/start.js +12 -16
- package/dist/cli/routes/daemon/status.js +12 -16
- package/dist/cli/routes/daemon/stop.js +12 -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} +177 -162
- package/dist/cli/routes/init.js +14 -18
- package/dist/cli/routes/keygen.js +12 -16
- package/dist/cli/routes/mcp.js +181 -165
- 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} +13 -17
- package/dist/cli/routes/sessions/ls.d.ts +4 -0
- package/dist/cli/routes/{ls.js → sessions/ls.js} +13 -17
- package/dist/cli/routes/sessions/revoke.d.ts +4 -0
- package/dist/cli/routes/{revoke.js → sessions/revoke.js} +14 -18
- package/dist/cli/routes/tools/call.d.ts +4 -0
- package/dist/cli/routes/{invoke.js → tools/call.js} +15 -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} +40 -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 +178 -138
- package/dist/commands/events.d.ts +31 -14
- 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 +1 -1
- package/dist/daemon/event-bus.d.ts +32 -0
- package/dist/daemon/rpc-errors.d.ts +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/events/index.d.ts +60 -0
- package/dist/index.js +56 -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 -27
- 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
|
@@ -208,7 +208,7 @@ import {
|
|
|
208
208
|
McpError,
|
|
209
209
|
ReadResourceRequestSchema
|
|
210
210
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
211
|
-
import { RPC_METHODS as
|
|
211
|
+
import { RPC_METHODS as RPC_METHODS7 } from "@appduct/shared";
|
|
212
212
|
|
|
213
213
|
// src/daemon/calls.ts
|
|
214
214
|
import {
|
|
@@ -2656,10 +2656,114 @@ var resolveAppTool = async (call, selector, name) => {
|
|
|
2656
2656
|
|
|
2657
2657
|
// src/mcp/events-tool.ts
|
|
2658
2658
|
import {
|
|
2659
|
-
RPC_METHODS as
|
|
2659
|
+
RPC_METHODS as RPC_METHODS6,
|
|
2660
|
+
toAppEvent as toAppEvent2
|
|
2660
2661
|
} from "@appduct/shared";
|
|
2662
|
+
|
|
2663
|
+
// src/events/index.ts
|
|
2664
|
+
import {
|
|
2665
|
+
RPC_METHODS as RPC_METHODS5,
|
|
2666
|
+
toAppEvent
|
|
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
|
|
2661
2763
|
var EVENTS_TOOL_NAME = "appduct_events";
|
|
2662
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;
|
|
2663
2767
|
var DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MS = 12e4;
|
|
2664
2768
|
var MAX_WAIT_FOR_EVENT_TIMEOUT_MS = 25 * 60 * 1e3;
|
|
2665
2769
|
var WAIT_FOR_EVENT_PROGRESS_INTERVAL_MS = 6e4;
|
|
@@ -2668,28 +2772,30 @@ var clampWaitForEventTimeoutMs = (requestedMs) => {
|
|
|
2668
2772
|
};
|
|
2669
2773
|
var EVENTS_TOOL_DESCRIPTOR = {
|
|
2670
2774
|
name: EVENTS_TOOL_NAME,
|
|
2671
|
-
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.`,
|
|
2672
2776
|
inputSchema: {
|
|
2673
2777
|
type: "object",
|
|
2674
2778
|
properties: {
|
|
2675
2779
|
selector: { type: "string" },
|
|
2676
2780
|
since: { type: "integer", minimum: 0 },
|
|
2677
|
-
limit: { type: "integer", exclusiveMinimum: 0 }
|
|
2781
|
+
limit: { type: "integer", exclusiveMinimum: 0 },
|
|
2782
|
+
name: { type: "string" },
|
|
2783
|
+
payloadMaxBytes: { type: "integer", exclusiveMinimum: 0 }
|
|
2678
2784
|
},
|
|
2679
2785
|
additionalProperties: false
|
|
2680
2786
|
}
|
|
2681
2787
|
};
|
|
2682
2788
|
var WAIT_FOR_EVENT_TOOL_DESCRIPTOR = {
|
|
2683
2789
|
name: WAIT_FOR_EVENT_TOOL_NAME,
|
|
2684
|
-
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.`,
|
|
2685
2791
|
inputSchema: {
|
|
2686
2792
|
type: "object",
|
|
2687
2793
|
properties: {
|
|
2688
2794
|
selector: { type: "string" },
|
|
2689
2795
|
name: { type: "string" },
|
|
2690
|
-
match: { type: "object", additionalProperties: { type: ["string", "number", "boolean", "null"] } },
|
|
2691
2796
|
since: { type: "integer", minimum: 0 },
|
|
2692
|
-
timeoutMs: { type: "number", exclusiveMinimum: 0 }
|
|
2797
|
+
timeoutMs: { type: "number", exclusiveMinimum: 0 },
|
|
2798
|
+
payloadMaxBytes: { type: "integer", exclusiveMinimum: 0 }
|
|
2693
2799
|
},
|
|
2694
2800
|
required: ["name"],
|
|
2695
2801
|
additionalProperties: false
|
|
@@ -2745,169 +2851,83 @@ var rejectKinds = (args) => {
|
|
|
2745
2851
|
);
|
|
2746
2852
|
}
|
|
2747
2853
|
};
|
|
2748
|
-
var
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
}
|
|
2755
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2756
|
-
throw new McpBuiltinToolError("invalid_request", '"match" must be a JSON object.');
|
|
2757
|
-
}
|
|
2758
|
-
const record = value;
|
|
2759
|
-
for (const [key, entry] of Object.entries(record)) {
|
|
2760
|
-
if (!isMatchPrimitive(entry)) {
|
|
2761
|
-
throw new McpBuiltinToolError(
|
|
2762
|
-
"invalid_request",
|
|
2763
|
-
`"match.${key}" must be a string, number, boolean, or null (objects/arrays can never match).`
|
|
2764
|
-
);
|
|
2765
|
-
}
|
|
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
|
+
);
|
|
2766
2860
|
}
|
|
2767
|
-
return record;
|
|
2768
2861
|
};
|
|
2769
2862
|
var handleEventsTool = async (rawArgs, deps) => {
|
|
2770
2863
|
const args = asRecord3(rawArgs);
|
|
2771
2864
|
rejectKinds(args);
|
|
2772
|
-
|
|
2865
|
+
const result = await deps.call(RPC_METHODS6.eventsSince, {
|
|
2773
2866
|
selector: asOptionalString2(args.selector, "selector"),
|
|
2774
2867
|
since: asOptionalNonNegativeInteger(args.since, "since"),
|
|
2775
|
-
limit: asOptionalPositiveInteger(args.limit, "limit")
|
|
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
|
|
2776
2874
|
});
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
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
|
+
};
|
|
2784
2881
|
};
|
|
2785
2882
|
var handleWaitForEventTool = async (rawArgs, deps) => {
|
|
2786
2883
|
const args = asRecord3(rawArgs);
|
|
2787
2884
|
rejectKinds(args);
|
|
2885
|
+
rejectMatch(args);
|
|
2788
2886
|
const selector = asOptionalString2(args.selector, "selector");
|
|
2789
2887
|
const name = asOptionalString2(args.name, "name");
|
|
2790
2888
|
if (!name) {
|
|
2791
2889
|
throw new McpBuiltinToolError("invalid_request", '"name" must be a non-empty string.');
|
|
2792
2890
|
}
|
|
2793
|
-
const match = asOptionalMatch(args.match);
|
|
2794
2891
|
const since = asOptionalNonNegativeInteger(args.since, "since");
|
|
2795
2892
|
const timeoutMs = clampWaitForEventTimeoutMs(asOptionalPositiveNumber2(args.timeoutMs, "timeoutMs"));
|
|
2893
|
+
const payloadMaxBytes = asOptionalPositiveInteger(args.payloadMaxBytes, "payloadMaxBytes") ?? DEFAULT_EVENTS_PAYLOAD_MAX_BYTES;
|
|
2796
2894
|
const stream = await openDaemonStream({ stateDir: deps.stateDir, spawn: deps.spawn });
|
|
2797
2895
|
try {
|
|
2798
|
-
const described = await stream.call(
|
|
2799
|
-
const
|
|
2800
|
-
const
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
return { sessionId, alias, name, payload: data.payload, ts: event.ts, seq: event.seq };
|
|
2813
|
-
};
|
|
2814
|
-
let liveHandler = null;
|
|
2815
|
-
const earlyEvents = [];
|
|
2816
|
-
const unsubscribeNotification = stream.onNotification((payload) => {
|
|
2817
|
-
const event = payload;
|
|
2818
|
-
if (liveHandler) {
|
|
2819
|
-
liveHandler(event);
|
|
2820
|
-
} else {
|
|
2821
|
-
earlyEvents.push(event);
|
|
2822
|
-
}
|
|
2823
|
-
});
|
|
2824
|
-
let unsubscribeClose = () => {
|
|
2825
|
-
};
|
|
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;
|
|
2826
2910
|
try {
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
since
|
|
2911
|
+
const { event, dropped } = await waitForAppEvent(stream, {
|
|
2912
|
+
sessionId: described.sessionId,
|
|
2913
|
+
name,
|
|
2914
|
+
since,
|
|
2915
|
+
timeoutMs,
|
|
2916
|
+
payloadMaxBytes
|
|
2831
2917
|
});
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
seenSeqs.add(event.seq);
|
|
2837
|
-
backlog.push(event);
|
|
2838
|
-
}
|
|
2918
|
+
return { ...event, dropped };
|
|
2919
|
+
} catch (error) {
|
|
2920
|
+
if (error instanceof WaitForAppEventTimeoutError) {
|
|
2921
|
+
throw new McpBuiltinToolError("tool_timeout", error.message);
|
|
2839
2922
|
}
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
const result = toResult(event);
|
|
2843
|
-
if (result) {
|
|
2844
|
-
return result;
|
|
2845
|
-
}
|
|
2923
|
+
if (error instanceof WaitForAppEventConnectionClosedError) {
|
|
2924
|
+
throw new McpBuiltinToolError("tool_execution_error", error.message);
|
|
2846
2925
|
}
|
|
2847
|
-
|
|
2848
|
-
return await new Promise((resolve2, reject) => {
|
|
2849
|
-
let settled = false;
|
|
2850
|
-
const settle = (fn) => {
|
|
2851
|
-
if (settled) {
|
|
2852
|
-
return;
|
|
2853
|
-
}
|
|
2854
|
-
settled = true;
|
|
2855
|
-
clearTimeout(timer);
|
|
2856
|
-
if (progressTimer) {
|
|
2857
|
-
clearInterval(progressTimer);
|
|
2858
|
-
}
|
|
2859
|
-
liveHandler = null;
|
|
2860
|
-
fn();
|
|
2861
|
-
};
|
|
2862
|
-
const timer = setTimeout(() => {
|
|
2863
|
-
settle(
|
|
2864
|
-
() => reject(
|
|
2865
|
-
new McpBuiltinToolError(
|
|
2866
|
-
"tool_timeout",
|
|
2867
|
-
`Timed out after ${timeoutMs}ms waiting for event "${name}".`
|
|
2868
|
-
)
|
|
2869
|
-
)
|
|
2870
|
-
);
|
|
2871
|
-
}, timeoutMs);
|
|
2872
|
-
const startedAt = Date.now();
|
|
2873
|
-
const progressTimer = deps.progress ? setInterval(() => {
|
|
2874
|
-
deps.progress.sendNotification({
|
|
2875
|
-
method: "notifications/progress",
|
|
2876
|
-
params: {
|
|
2877
|
-
progressToken: deps.progress.token,
|
|
2878
|
-
progress: Date.now() - startedAt,
|
|
2879
|
-
total: timeoutMs,
|
|
2880
|
-
message: `Still waiting for event "${name}"...`
|
|
2881
|
-
}
|
|
2882
|
-
}).catch(() => {
|
|
2883
|
-
});
|
|
2884
|
-
}, WAIT_FOR_EVENT_PROGRESS_INTERVAL_MS) : void 0;
|
|
2885
|
-
unsubscribeClose = stream.onClose(() => {
|
|
2886
|
-
settle(
|
|
2887
|
-
() => reject(
|
|
2888
|
-
new McpBuiltinToolError(
|
|
2889
|
-
"tool_execution_error",
|
|
2890
|
-
`The connection to the Appduct daemon closed while waiting for event "${name}".`
|
|
2891
|
-
)
|
|
2892
|
-
)
|
|
2893
|
-
);
|
|
2894
|
-
});
|
|
2895
|
-
liveHandler = (event) => {
|
|
2896
|
-
if (settled) {
|
|
2897
|
-
return;
|
|
2898
|
-
}
|
|
2899
|
-
if (event.sessionId === sessionId && event.seq <= highestConsideredSeq) {
|
|
2900
|
-
return;
|
|
2901
|
-
}
|
|
2902
|
-
const result = toResult(event);
|
|
2903
|
-
if (result) {
|
|
2904
|
-
settle(() => resolve2(result));
|
|
2905
|
-
}
|
|
2906
|
-
};
|
|
2907
|
-
});
|
|
2926
|
+
throw error;
|
|
2908
2927
|
} finally {
|
|
2909
|
-
|
|
2910
|
-
|
|
2928
|
+
if (progressTimer) {
|
|
2929
|
+
clearInterval(progressTimer);
|
|
2930
|
+
}
|
|
2911
2931
|
}
|
|
2912
2932
|
} finally {
|
|
2913
2933
|
stream.close();
|
|
@@ -3047,7 +3067,7 @@ var createMcpServer = async (options) => {
|
|
|
3047
3067
|
}
|
|
3048
3068
|
if (progressToken === void 0) {
|
|
3049
3069
|
const result = await stream.call(
|
|
3050
|
-
|
|
3070
|
+
RPC_METHODS7.toolsCall,
|
|
3051
3071
|
{
|
|
3052
3072
|
selector: tool.sessionId,
|
|
3053
3073
|
name: tool.descriptor.name,
|
|
@@ -3062,7 +3082,7 @@ var createMcpServer = async (options) => {
|
|
|
3062
3082
|
}
|
|
3063
3083
|
const progressStream = await openStream({ stateDir: options.stateDir, spawn: options.spawn });
|
|
3064
3084
|
try {
|
|
3065
|
-
await progressStream.call(
|
|
3085
|
+
await progressStream.call(RPC_METHODS7.eventsSubscribe, {
|
|
3066
3086
|
sessionSelector: tool.sessionId,
|
|
3067
3087
|
kinds: ["tool_call_started", "tool_call_progress"]
|
|
3068
3088
|
});
|
|
@@ -3072,7 +3092,7 @@ var createMcpServer = async (options) => {
|
|
|
3072
3092
|
if (callId === void 0 || !cancelRequested) {
|
|
3073
3093
|
return;
|
|
3074
3094
|
}
|
|
3075
|
-
progressStream.call(
|
|
3095
|
+
progressStream.call(RPC_METHODS7.toolsCancel, { selector: tool.sessionId, callId, reason: "mcp_client_cancelled" }).catch((error) => {
|
|
3076
3096
|
console.error("appduct mcp: failed to cancel an app tool call:", error);
|
|
3077
3097
|
});
|
|
3078
3098
|
};
|
|
@@ -3105,7 +3125,7 @@ var createMcpServer = async (options) => {
|
|
|
3105
3125
|
});
|
|
3106
3126
|
try {
|
|
3107
3127
|
const result = await progressStream.call(
|
|
3108
|
-
|
|
3128
|
+
RPC_METHODS7.toolsCall,
|
|
3109
3129
|
{
|
|
3110
3130
|
selector: tool.sessionId,
|
|
3111
3131
|
name: tool.descriptor.name,
|
|
@@ -3217,7 +3237,7 @@ var createMcpServer = async (options) => {
|
|
|
3217
3237
|
if (request.params.uri !== SESSIONS_RESOURCE_URI) {
|
|
3218
3238
|
throw new Error(`Unknown resource "${request.params.uri}".`);
|
|
3219
3239
|
}
|
|
3220
|
-
const sessions = await stream.call(
|
|
3240
|
+
const sessions = await stream.call(RPC_METHODS7.sessionsList);
|
|
3221
3241
|
return {
|
|
3222
3242
|
contents: [{ uri: SESSIONS_RESOURCE_URI, mimeType: "application/json", text: JSON.stringify(sessions) }]
|
|
3223
3243
|
};
|
|
@@ -3389,9 +3409,6 @@ var renderLsData = (colors, data, now) => {
|
|
|
3389
3409
|
...rows.map((row) => ` ${row.map((cell, index) => cell.padEnd(widths[index])).join(" ")}`)
|
|
3390
3410
|
];
|
|
3391
3411
|
};
|
|
3392
|
-
var isToolsListing = (data) => {
|
|
3393
|
-
return typeof data === "object" && data !== null && Array.isArray(data.tools);
|
|
3394
|
-
};
|
|
3395
3412
|
var isToolGroupsListing = (data) => {
|
|
3396
3413
|
return typeof data === "object" && data !== null && Array.isArray(data.groups) && !Array.isArray(data.tools);
|
|
3397
3414
|
};
|
|
@@ -3409,7 +3426,7 @@ var renderEmptyToolsLine = (data) => {
|
|
|
3409
3426
|
}
|
|
3410
3427
|
if (data.group !== void 0) {
|
|
3411
3428
|
const match = data.filter === void 0 ? "" : ` match ${JSON.stringify(data.filter)}`;
|
|
3412
|
-
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.`;
|
|
3413
3430
|
}
|
|
3414
3431
|
return data.filter === void 0 ? " No tools registered." : ` No tools match ${JSON.stringify(data.filter)}.`;
|
|
3415
3432
|
};
|
|
@@ -3491,7 +3508,7 @@ var renderToolSummaryTable = (colors, data) => {
|
|
|
3491
3508
|
...grouped ? renderGroupedToolLines(colors, data.tools) : data.tools.flatMap((tool) => renderToolSummaryLines(tool, " ")),
|
|
3492
3509
|
...renderTruncationLine(data),
|
|
3493
3510
|
"",
|
|
3494
|
-
"Run `appduct tools <name>` for a tool's full schema."
|
|
3511
|
+
"Run `appduct tools describe <name>` for a tool's full schema."
|
|
3495
3512
|
];
|
|
3496
3513
|
};
|
|
3497
3514
|
var renderToolDetail = (colors, tool, flags) => {
|
|
@@ -3543,16 +3560,13 @@ var renderToolGroups = (colors, data) => {
|
|
|
3543
3560
|
colors.green("Groups"),
|
|
3544
3561
|
...data.groups.map((entry) => ` ${label(entry).padEnd(width)} ${String(entry.total).padStart(countWidth)}`),
|
|
3545
3562
|
"",
|
|
3546
|
-
`${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.")
|
|
3547
3564
|
];
|
|
3548
3565
|
};
|
|
3549
|
-
var
|
|
3566
|
+
var renderToolsListData = (colors, data, flags, full) => {
|
|
3550
3567
|
if (isToolGroupsListing(data)) {
|
|
3551
3568
|
return renderToolGroups(colors, data);
|
|
3552
3569
|
}
|
|
3553
|
-
if (!isToolsListing(data)) {
|
|
3554
|
-
return renderToolDetail(colors, data, flags);
|
|
3555
|
-
}
|
|
3556
3570
|
return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
|
|
3557
3571
|
};
|
|
3558
3572
|
var renderInvokeData = (colors, data, flags) => {
|
|
@@ -3738,15 +3752,17 @@ var renderSuccessData = (colors, command, data, options) => {
|
|
|
3738
3752
|
return renderInitData(colors, data, flags);
|
|
3739
3753
|
case "keygen":
|
|
3740
3754
|
return renderKeygenData(colors, data, flags);
|
|
3741
|
-
case "link":
|
|
3755
|
+
case "sessions link":
|
|
3742
3756
|
return renderLinkData(colors, data, flags, options.qr);
|
|
3743
|
-
case "ls":
|
|
3757
|
+
case "sessions ls":
|
|
3744
3758
|
return renderLsData(colors, data, options.now ?? /* @__PURE__ */ new Date());
|
|
3745
|
-
case "tools":
|
|
3746
|
-
return
|
|
3747
|
-
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":
|
|
3748
3764
|
return renderInvokeData(colors, data, flags);
|
|
3749
|
-
case "revoke":
|
|
3765
|
+
case "sessions revoke":
|
|
3750
3766
|
return renderRevokeData(colors, data);
|
|
3751
3767
|
case "daemon run":
|
|
3752
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;
|