@agentprojectcontext/apx 1.48.0 → 1.48.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.
@@ -18,7 +18,7 @@
18
18
  <link rel="apple-touch-icon" href="/favicon/dark/apple-touch-icon.png" media="(prefers-color-scheme: dark)" />
19
19
  <link rel="manifest" href="/favicon/white/site.webmanifest" media="(prefers-color-scheme: light)" />
20
20
  <link rel="manifest" href="/favicon/dark/site.webmanifest" media="(prefers-color-scheme: dark)" />
21
- <script type="module" crossorigin src="/assets/index-C9O1GTZ_.js"></script>
21
+ <script type="module" crossorigin src="/assets/index-BDJfFzQk.js"></script>
22
22
  <link rel="stylesheet" crossorigin href="/assets/index-CilEtMjV.css">
23
23
  </head>
24
24
  <body class="bg-background text-foreground antialiased">
@@ -1,4 +1,4 @@
1
- import { http } from "../http";
1
+ import { http, unwrapPage } from "../http";
2
2
 
3
3
  export interface SessionRow {
4
4
  engine: string;
@@ -10,15 +10,15 @@ export interface SessionRow {
10
10
  }
11
11
 
12
12
  export const Sessions = {
13
- // Cross-engine sessions (apx · claude · codex), newest first.
13
+ // Cross-engine sessions (apx · claude · codex), newest first — full set.
14
14
  global: (engine?: string) =>
15
- http.get<{ sessions: SessionRow[] }>(`/sessions${engine ? `?engine=${encodeURIComponent(engine)}` : ""}`),
15
+ http
16
+ .get<unknown>(`/sessions${engine ? `?engine=${encodeURIComponent(engine)}` : ""}`)
17
+ .then((b) => ({ sessions: unwrapPage<SessionRow>(b).items })),
16
18
  // Server-paginated page: returns the requested window plus the full total.
17
19
  page: ({ engine, limit, offset }: { engine?: string; limit: number; offset: number }) => {
18
20
  const q = new URLSearchParams({ limit: String(limit), offset: String(offset) });
19
21
  if (engine) q.set("engine", engine);
20
- return http
21
- .getWithTotal<{ sessions: SessionRow[] }>(`/sessions?${q.toString()}`)
22
- .then((r) => ({ items: r.data.sessions, total: r.total }));
22
+ return http.get<unknown>(`/sessions?${q.toString()}`).then((b) => unwrapPage<SessionRow>(b));
23
23
  },
24
24
  };
@@ -1,4 +1,4 @@
1
- import { http } from "../http";
1
+ import { http, unwrapPage } from "../http";
2
2
  import type { TaskEntry } from "../../types/daemon";
3
3
 
4
4
  export interface GlobalTaskEntry extends TaskEntry {
@@ -7,20 +7,17 @@ export interface GlobalTaskEntry extends TaskEntry {
7
7
  }
8
8
 
9
9
  export const Tasks = {
10
+ // Full sets (no pagination) — unwrapped to plain arrays for non-paged callers.
10
11
  list: (pid: string, state: TaskEntry["state"] | "all" = "open") =>
11
- http.get<TaskEntry[]>(`/projects/${pid}/tasks?state=${state}`),
12
+ http.get<unknown>(`/projects/${pid}/tasks?state=${state}`).then((b) => unwrapPage<TaskEntry>(b).items),
12
13
  global: (state: TaskEntry["state"] | "all" = "open") =>
13
- http.get<GlobalTaskEntry[]>(`/tasks?state=${state}`),
14
+ http.get<unknown>(`/tasks?state=${state}`).then((b) => unwrapPage<GlobalTaskEntry>(b).items),
14
15
  // Server-paginated variants: one project (listPage) or all projects
15
16
  // (globalPage). Each returns the requested window plus the full total.
16
17
  listPage: (pid: string, { state, limit, offset }: { state: TaskEntry["state"] | "all"; limit: number; offset: number }) =>
17
- http
18
- .getWithTotal<TaskEntry[]>(`/projects/${pid}/tasks?state=${state}&limit=${limit}&offset=${offset}`)
19
- .then((r) => ({ items: r.data, total: r.total })),
18
+ http.get<unknown>(`/projects/${pid}/tasks?state=${state}&limit=${limit}&offset=${offset}`).then((b) => unwrapPage<TaskEntry>(b)),
20
19
  globalPage: ({ state, limit, offset }: { state: TaskEntry["state"] | "all"; limit: number; offset: number }) =>
21
- http
22
- .getWithTotal<GlobalTaskEntry[]>(`/tasks?state=${state}&limit=${limit}&offset=${offset}`)
23
- .then((r) => ({ items: r.data, total: r.total })),
20
+ http.get<unknown>(`/tasks?state=${state}&limit=${limit}&offset=${offset}`).then((b) => unwrapPage<GlobalTaskEntry>(b)),
24
21
  add: (pid: string, body: Partial<TaskEntry>) =>
25
22
  http.post<TaskEntry>(`/projects/${pid}/tasks`, body),
26
23
  done: (pid: string, id: string) => http.post<TaskEntry>(`/projects/${pid}/tasks/${id}/done`),
@@ -52,38 +52,36 @@ async function request<T>(
52
52
  return (await res.json()) as T;
53
53
  }
54
54
 
55
- // GET that also surfaces the total-row count for server-side pagination. The
56
- // daemon returns the full count in the X-Total-Count header (the body keeps its
57
- // normal shape); we fall back to the payload length when the header is absent
58
- // (e.g. an older daemon) so pagination degrades gracefully instead of breaking.
59
- async function getWithTotal<T>(path: string): Promise<{ data: T; total: number }> {
60
- const headers: Record<string, string> = token ? { authorization: `Bearer ${token}` } : {};
61
- const res = await fetch(path, { method: "GET", headers });
62
- if (!res.ok) {
63
- let detail = "";
64
- let parsed: unknown = null;
65
- try {
66
- parsed = await res.json();
67
- detail = (parsed as { error?: string })?.error || JSON.stringify(parsed);
68
- } catch {
69
- detail = await res.text();
70
- }
71
- throw new HttpError(res.status, `GET ${path} ${res.status}: ${detail}`, parsed);
55
+ // Pagination metadata returned by list endpoints in the { meta, data } envelope.
56
+ export interface PageMeta {
57
+ total: number;
58
+ offset: number;
59
+ limit: number | null;
60
+ pageSize: number;
61
+ page: number;
62
+ pageCount: number;
63
+ }
64
+
65
+ // Normalize any list response into { items, total }. Accepts the { meta, data }
66
+ // envelope (current daemon), a bare array, or the legacy { sessions } object, so
67
+ // the UI keeps working across a daemon that hasn't been restarted yet (it just
68
+ // degrades to a single page when no meta.total is present).
69
+ export function unwrapPage<T>(body: unknown): { items: T[]; total: number } {
70
+ const b = body as { data?: unknown; meta?: { total?: number }; sessions?: unknown };
71
+ if (Array.isArray(body)) return { items: body as T[], total: body.length };
72
+ if (b && Array.isArray(b.data)) {
73
+ const items = b.data as T[];
74
+ return { items, total: typeof b.meta?.total === "number" ? b.meta.total : items.length };
75
+ }
76
+ if (b && Array.isArray(b.sessions)) {
77
+ const items = b.sessions as T[];
78
+ return { items, total: items.length };
72
79
  }
73
- const data = (await res.json()) as T;
74
- const header = res.headers.get("X-Total-Count");
75
- const total =
76
- header != null && header !== ""
77
- ? parseInt(header, 10)
78
- : Array.isArray(data)
79
- ? data.length
80
- : 0;
81
- return { data, total };
80
+ return { items: [], total: 0 };
82
81
  }
83
82
 
84
83
  export const http = {
85
84
  get: <T>(p: string) => request<T>("GET", p),
86
- getWithTotal,
87
85
  post: <T>(p: string, b?: unknown) => request<T>("POST", p, b),
88
86
  put: <T>(p: string, b?: unknown) => request<T>("PUT", p, b),
89
87
  patch: <T>(p: string, b?: unknown) => request<T>("PATCH", p, b),