appduct 0.15.1 → 0.16.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.
@@ -8,7 +8,7 @@
8
8
  * (`mcp/events-tool.ts`) uses — called on a fresh stream opened for that one wait, so concurrent
9
9
  * waits on the same `AppClient` never share (and can't clobber) one connection's subscription.
10
10
  */
11
- import { type AppEvent, type FullAppEvent, type ListedToolDescriptor } from "@appduct/shared";
11
+ import { type AppEvent, type FullAppEvent, type ListedToolDescriptor, type SessionState } from "@appduct/shared";
12
12
  import type { DaemonStream } from "../rpc/client.js";
13
13
  /** A project's own tool map — declared once, e.g. `type Tools = { sum: { args: { a: number; b:
14
14
  * number }; result: { total: number } } }` — to get typed {@link AppClient.call} throughout a test
@@ -93,6 +93,13 @@ export type EventsResult = EventsCounts & {
93
93
  export type { AppEvent, FullAppEvent } from "@appduct/shared";
94
94
  export type AppClient<TTools = ToolMap> = {
95
95
  readonly sessionId: string;
96
+ /** The session's state now: `active` while the app is connected, `suspended` while it is away
97
+ * (its process died, it was backgrounded or a page closed) and may still come back. Rejects with
98
+ * `"unknown_session"` once the session is gone. */
99
+ state(): Promise<SessionState>;
100
+ /** Ends the session: the daemon disconnects the app and forgets the session, so it can never
101
+ * resume. Works in any state. */
102
+ revoke(): Promise<void>;
96
103
  /** `tools.list` for this session. An entry spells an ungrouped tool's `group` as `null`,
97
104
  * where a registration omits it. */
98
105
  tools(): Promise<ListedToolDescriptor[]>;
@@ -17,4 +17,4 @@ export { link, waitForSession, type LinkOptions, type LinkResult, type WaitForSe
17
17
  export { AppductError, type AppductErrorType } from "./errors.js";
18
18
  export type { AppClient, AppEvent, CallOptions, EventsOptions, EventsResult, FullAppEvent, FullEventsResult, ToolMap, WaitForEventOptions, } from "./app-client.js";
19
19
  export type { TruncatedAppEvent } from "@appduct/shared";
20
- export type { AgentEndpoint, ErrorType, ListedToolDescriptor, ToolDescriptor } from "@appduct/shared";
20
+ export type { AgentEndpoint, ErrorType, ListedToolDescriptor, SessionState, ToolDescriptor } from "@appduct/shared";
@@ -1332,6 +1332,20 @@ var makeAppClient = (stream, sessionId, openStream) => {
1332
1332
  const openWaitStreams = /* @__PURE__ */ new Set();
1333
1333
  const client = {
1334
1334
  sessionId,
1335
+ state: async () => {
1336
+ try {
1337
+ return (await stream.call(RPC_METHODS3.sessionsDescribe, { selector: sessionId })).state;
1338
+ } catch (error) {
1339
+ throw toAppductError(error);
1340
+ }
1341
+ },
1342
+ revoke: async () => {
1343
+ try {
1344
+ await stream.call(RPC_METHODS3.sessionsRevoke, { selector: sessionId });
1345
+ } catch (error) {
1346
+ throw toAppductError(error);
1347
+ }
1348
+ },
1335
1349
  tools: async () => {
1336
1350
  try {
1337
1351
  const result = await stream.call(RPC_METHODS3.toolsList, {
@@ -2540,14 +2554,6 @@ var waitForSession = async (sessionId, options = {}) => {
2540
2554
  }
2541
2555
  const openStream = () => openDaemonStream({ stateDir, spawn: options.spawn, autoSpawn: options.autoSpawn });
2542
2556
  try {
2543
- try {
2544
- await stream.call(RPC_METHODS6.sessionsDescribe, { selector: sessionId });
2545
- return makeAppClient(stream, sessionId, openStream);
2546
- } catch (error) {
2547
- if (!(error instanceof DaemonRpcError) || error.data?.type !== "unknown_session") {
2548
- throw error;
2549
- }
2550
- }
2551
2557
  await new Promise((resolve2, reject) => {
2552
2558
  let settled = false;
2553
2559
  const settle = (fn) => {
@@ -2589,9 +2595,16 @@ var waitForSession = async (sessionId, options = {}) => {
2589
2595
  settle(resolve2);
2590
2596
  }
2591
2597
  });
2592
- stream.call(RPC_METHODS6.eventsSubscribe, { sessionSelector: sessionId, kinds: ["session_claimed"] }).catch((error) => {
2593
- settle(() => reject(toAppductError(error)));
2594
- });
2598
+ const fail = (error) => settle(() => reject(toAppductError(error)));
2599
+ stream.call(RPC_METHODS6.eventsSubscribe, { sessionSelector: sessionId, kinds: ["session_claimed"] }).then(() => {
2600
+ stream.call(RPC_METHODS6.sessionsDescribe, { selector: sessionId }).then(
2601
+ () => settle(resolve2),
2602
+ // Not claimed yet: the subscription reports the claim when it comes.
2603
+ (error) => {
2604
+ if (!(error instanceof DaemonRpcError) || error.data?.type !== "unknown_session") fail(error);
2605
+ }
2606
+ );
2607
+ }, fail);
2595
2608
  });
2596
2609
  return makeAppClient(stream, sessionId, openStream);
2597
2610
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appduct",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "Call functions inside your running iOS, Android or React Native app from a terminal, test or AI agent.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -47,7 +47,7 @@
47
47
  "reflect-metadata": "^0.2.2",
48
48
  "toqr": "^0.1.1",
49
49
  "ws": "^8.18.3",
50
- "@appduct/shared": "0.15.1"
50
+ "@appduct/shared": "0.16.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "^22.10.0",
@@ -61,8 +61,8 @@
61
61
  "typescript": "~5.9.2",
62
62
  "vite": "^8.1.4",
63
63
  "webpack": "^5.111.1",
64
- "@appduct/web": "0.15.1",
65
- "@appduct/react-native": "0.15.1"
64
+ "@appduct/web": "0.16.0",
65
+ "@appduct/react-native": "0.16.0"
66
66
  },
67
67
  "files": [
68
68
  "bin.js",