@stigmer/ink 3.0.8-dev.20260613074252 → 3.0.8

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.
@@ -0,0 +1,33 @@
1
+ import type { SearchResult } from "@stigmer/protos/ai/stigmer/search/v1/io_pb";
2
+ /** Props for {@link AgentPicker}. */
3
+ export interface AgentPickerProps {
4
+ /** Organization to search within. */
5
+ readonly org: string;
6
+ /** Seeds the search box (e.g. an unresolved slug the user already typed). */
7
+ readonly initialQuery?: string;
8
+ /** `"org"` (default) searches the org; `"all"` includes public agents. */
9
+ readonly scope?: "org" | "all";
10
+ /** Called with the chosen agent's search result on selection. */
11
+ readonly onSelect: (agent: SearchResult) => void;
12
+ /** Called when the user cancels (Esc / Ctrl+C). */
13
+ readonly onCancel: () => void;
14
+ }
15
+ /**
16
+ * Interactive picker for choosing an agent by name.
17
+ *
18
+ * Composes the {@link useAgentSearch} data hook with {@link ResourcePicker}:
19
+ * the user types, results stream in (debounced by the hook), and the selected
20
+ * agent's {@link SearchResult} is returned via `onSelect`. Use the `id` field of
21
+ * that result to resolve and run the agent.
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * render(
26
+ * <InkStigmerProvider client={client}>
27
+ * <AgentPicker org="acme" onSelect={(a) => run(a.id)} onCancel={() => process.exit(0)} />
28
+ * </InkStigmerProvider>,
29
+ * );
30
+ * ```
31
+ */
32
+ export declare function AgentPicker({ org, initialQuery, scope, onSelect, onCancel }: AgentPickerProps): import("react/jsx-runtime").JSX.Element;
33
+ //# sourceMappingURL=AgentPicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentPicker.d.ts","sourceRoot":"","sources":["../../src/components/AgentPicker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAI/E,qCAAqC;AACrC,MAAM,WAAW,gBAAgB;IAC/B,qCAAqC;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACjD,mDAAmD;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC/B;AAKD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,EAAE,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,gBAAgB,2CAkC7F"}
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo } from "react";
3
+ import { useAgentSearch } from "@stigmer/react";
4
+ import { ResourcePicker } from "./ResourcePicker.js";
5
+ /** Max description length shown as the row subtitle before truncation. */
6
+ const SUBTITLE_MAX = 60;
7
+ /**
8
+ * Interactive picker for choosing an agent by name.
9
+ *
10
+ * Composes the {@link useAgentSearch} data hook with {@link ResourcePicker}:
11
+ * the user types, results stream in (debounced by the hook), and the selected
12
+ * agent's {@link SearchResult} is returned via `onSelect`. Use the `id` field of
13
+ * that result to resolve and run the agent.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * render(
18
+ * <InkStigmerProvider client={client}>
19
+ * <AgentPicker org="acme" onSelect={(a) => run(a.id)} onCancel={() => process.exit(0)} />
20
+ * </InkStigmerProvider>,
21
+ * );
22
+ * ```
23
+ */
24
+ export function AgentPicker({ org, initialQuery, scope, onSelect, onCancel }) {
25
+ const { results, isLoading, error, query, setQuery } = useAgentSearch(org, { scope });
26
+ // Seed the typed-but-unresolved query once on mount so the user picks up
27
+ // where the smart-resolution attempt left off.
28
+ useEffect(() => {
29
+ if (initialQuery !== undefined && initialQuery !== "")
30
+ setQuery(initialQuery);
31
+ // Mount-only: re-seeding on every render would fight the user's typing.
32
+ // eslint-disable-next-line react-hooks/exhaustive-deps
33
+ }, []);
34
+ const items = useMemo(() => results.map(toAgentItem), [results]);
35
+ const handleSelect = useCallback((item) => {
36
+ const found = results.find((r) => r.id === item.id);
37
+ if (found !== undefined)
38
+ onSelect(found);
39
+ }, [results, onSelect]);
40
+ return (_jsx(ResourcePicker, { prompt: "Select an agent", items: items, query: query, onQueryChange: setQuery, isLoading: isLoading, error: error, onSelect: handleSelect, onCancel: onCancel, emptyLabel: "no agents found" }));
41
+ }
42
+ /** Map a search result to a picker row: org/slug title, description subtitle. */
43
+ function toAgentItem(result) {
44
+ const title = result.qualifiedSlug !== "" ? result.qualifiedSlug : `${result.org}/${result.slug}`;
45
+ return { id: result.id, title, subtitle: truncate(result.description, SUBTITLE_MAX) };
46
+ }
47
+ function truncate(text, max) {
48
+ return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
49
+ }
50
+ //# sourceMappingURL=AgentPicker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentPicker.js","sourceRoot":"","sources":["../../src/components/AgentPicker.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAE/D,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAmB,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAgBtE,0EAA0E;AAC1E,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAoB;IAC5F,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAEtF,yEAAyE;IACzE,+CAA+C;IAC/C,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,KAAK,EAAE;YAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;QAC9E,wEAAwE;QACxE,uDAAuD;IACzD,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,KAAK,GAAG,OAAO,CAAe,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAE/E,MAAM,YAAY,GAAG,WAAW,CAC9B,CAAC,IAAgB,EAAE,EAAE;QACnB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;QACpD,IAAI,KAAK,KAAK,SAAS;YAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC,EACD,CAAC,OAAO,EAAE,QAAQ,CAAC,CACpB,CAAC;IAEF,OAAO,CACL,KAAC,cAAc,IACb,MAAM,EAAC,iBAAiB,EACxB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,EACZ,aAAa,EAAE,QAAQ,EACvB,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAC,iBAAiB,GAC5B,CACH,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,SAAS,WAAW,CAAC,MAAoB;IACvC,MAAM,KAAK,GAAG,MAAM,CAAC,aAAa,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IAClG,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,YAAY,CAAC,EAAE,CAAC;AACxF,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,GAAW;IACzC,OAAO,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;AAClE,CAAC"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * A single selectable entry in a {@link ResourcePicker}.
3
+ *
4
+ * Callers map their domain objects (agents, sessions, ...) into this shape and
5
+ * receive the chosen item back via `onSelect`. The picker is domain-agnostic:
6
+ * it knows only how to render and navigate these rows.
7
+ */
8
+ export interface PickerItem {
9
+ /** Opaque identity returned to the caller on selection. */
10
+ readonly id: string;
11
+ /** Primary display text, rendered bold when active (e.g. "acme/deploy"). */
12
+ readonly title: string;
13
+ /** Secondary text, rendered dimmed after the title (e.g. a description). */
14
+ readonly subtitle?: string;
15
+ /** Right-aligned metadata, rendered dimmed (e.g. "2 hours ago"). */
16
+ readonly meta?: string;
17
+ }
18
+ /** Props for {@link ResourcePicker}. */
19
+ export interface ResourcePickerProps {
20
+ /** The current result rows to choose from (owned by the caller). */
21
+ readonly items: readonly PickerItem[];
22
+ /** Current query text (controlled — the caller owns search state). */
23
+ readonly query: string;
24
+ /** Called when the query text changes; the caller re-runs its search. */
25
+ readonly onQueryChange: (query: string) => void;
26
+ /** Called with the chosen item when the user presses Enter. */
27
+ readonly onSelect: (item: PickerItem) => void;
28
+ /** Called when the user cancels (Esc or Ctrl+C). */
29
+ readonly onCancel: () => void;
30
+ /** `true` while a search request is in flight. */
31
+ readonly isLoading?: boolean;
32
+ /** Error from the last failed search, shown in place of the list. */
33
+ readonly error?: Error | null;
34
+ /** Label shown before the input (e.g. "Select an agent"). */
35
+ readonly prompt?: string;
36
+ /** Placeholder shown in the empty input. */
37
+ readonly placeholder?: string;
38
+ /** Label shown when a completed search returns no rows. */
39
+ readonly emptyLabel?: string;
40
+ }
41
+ /**
42
+ * Generic, interactive search-and-select list for the terminal.
43
+ *
44
+ * Renders a text input above a scrollable result list. The user types to
45
+ * filter (the caller performs the actual search via `onQueryChange`), navigates
46
+ * with the arrow keys, selects with Enter, and cancels with Esc. It holds no
47
+ * data of its own — pass `items`/`query`/`isLoading` from a data hook such as
48
+ * `useAgentSearch` or `useSessionList`.
49
+ *
50
+ * This is the reusable organism behind {@link AgentPicker} and
51
+ * {@link SessionPicker}; platform builders can compose it with any search
52
+ * function to pick any resource.
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * const { results, isLoading, query, setQuery } = useAgentSearch("acme");
57
+ * const items = results.map((r) => ({ id: r.id, title: r.qualifiedSlug }));
58
+ * render(
59
+ * <ResourcePicker
60
+ * prompt="Select an agent"
61
+ * items={items}
62
+ * query={query}
63
+ * onQueryChange={setQuery}
64
+ * isLoading={isLoading}
65
+ * onSelect={(item) => console.log(item.id)}
66
+ * onCancel={() => process.exit(0)}
67
+ * />,
68
+ * );
69
+ * ```
70
+ */
71
+ export declare function ResourcePicker({ items, query, onQueryChange, onSelect, onCancel, isLoading, error, prompt, placeholder, emptyLabel, }: ResourcePickerProps): import("react/jsx-runtime").JSX.Element;
72
+ //# sourceMappingURL=ResourcePicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResourcePicker.d.ts","sourceRoot":"","sources":["../../src/components/ResourcePicker.tsx"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,oEAAoE;IACpE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,sEAAsE;IACtE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IAC9B,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,4CAA4C;IAC5C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,2DAA2D;IAC3D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,KAAK,EACL,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,SAAiB,EACjB,KAAY,EACZ,MAAiB,EACjB,WAAiC,EACjC,UAAyB,GAC1B,EAAE,mBAAmB,2CA6DrB"}
@@ -0,0 +1,90 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ import TextInput from "ink-text-input";
5
+ /** Maximum rows shown at once; the list scrolls a window around the cursor. */
6
+ const MAX_VISIBLE = 10;
7
+ /**
8
+ * Generic, interactive search-and-select list for the terminal.
9
+ *
10
+ * Renders a text input above a scrollable result list. The user types to
11
+ * filter (the caller performs the actual search via `onQueryChange`), navigates
12
+ * with the arrow keys, selects with Enter, and cancels with Esc. It holds no
13
+ * data of its own — pass `items`/`query`/`isLoading` from a data hook such as
14
+ * `useAgentSearch` or `useSessionList`.
15
+ *
16
+ * This is the reusable organism behind {@link AgentPicker} and
17
+ * {@link SessionPicker}; platform builders can compose it with any search
18
+ * function to pick any resource.
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * const { results, isLoading, query, setQuery } = useAgentSearch("acme");
23
+ * const items = results.map((r) => ({ id: r.id, title: r.qualifiedSlug }));
24
+ * render(
25
+ * <ResourcePicker
26
+ * prompt="Select an agent"
27
+ * items={items}
28
+ * query={query}
29
+ * onQueryChange={setQuery}
30
+ * isLoading={isLoading}
31
+ * onSelect={(item) => console.log(item.id)}
32
+ * onCancel={() => process.exit(0)}
33
+ * />,
34
+ * );
35
+ * ```
36
+ */
37
+ export function ResourcePicker({ items, query, onQueryChange, onSelect, onCancel, isLoading = false, error = null, prompt = "Search", placeholder = "type to search...", emptyLabel = "no results", }) {
38
+ const [cursor, setCursor] = useState(0);
39
+ // Reset the cursor to the top whenever the query changes — the result set is
40
+ // about to be replaced, so a stale cursor would point at the wrong row.
41
+ useEffect(() => {
42
+ setCursor(0);
43
+ }, [query]);
44
+ const active = items.length === 0 ? 0 : Math.min(cursor, items.length - 1);
45
+ useInput((input, key) => {
46
+ if (key.escape || (key.ctrl && input === "c")) {
47
+ onCancel();
48
+ return;
49
+ }
50
+ if (key.return) {
51
+ if (items.length > 0)
52
+ onSelect(items[active]);
53
+ return;
54
+ }
55
+ if (key.upArrow) {
56
+ setCursor((c) => Math.max(0, Math.min(c, items.length - 1) - 1));
57
+ return;
58
+ }
59
+ if (key.downArrow) {
60
+ setCursor((c) => Math.min(items.length - 1, c + 1));
61
+ }
62
+ });
63
+ const [start, end] = visibleWindow(active, items.length);
64
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: "?" }), _jsxs(Text, { bold: true, children: [prompt, ":"] }), _jsx(TextInput, { value: query, onChange: onQueryChange, placeholder: placeholder })] }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: error !== null ? (_jsx(Text, { color: "red", children: ` Error: ${error.message}` })) : isLoading ? (_jsx(Text, { dimColor: true, children: " searching..." })) : items.length === 0 ? (_jsx(Text, { dimColor: true, children: ` ${emptyLabel}` })) : (_jsxs(_Fragment, { children: [items.slice(start, end).map((item, i) => (_jsx(PickerRow, { item: item, isActive: start + i === active }, item.id))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: ` ${countLabel(items.length)} | up/down: navigate | enter: select | esc: cancel` }) })] })) })] }));
65
+ }
66
+ /** One result row: a cursor glyph, the title, then optional subtitle/meta. */
67
+ function PickerRow({ item, isActive }) {
68
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isActive ? "cyan" : undefined, children: isActive ? "❯ " : " " }), _jsx(Text, { color: isActive ? "cyan" : undefined, bold: isActive, children: item.title }), item.subtitle !== undefined && item.subtitle !== "" ? _jsx(Text, { dimColor: true, children: ` ${item.subtitle}` }) : null, item.meta !== undefined && item.meta !== "" ? _jsx(Text, { dimColor: true, children: ` ${item.meta}` }) : null] }));
69
+ }
70
+ /** "1 result" / "N results" — matches the Go picker's footer wording. */
71
+ function countLabel(count) {
72
+ return `${count} result${count === 1 ? "" : "s"}`;
73
+ }
74
+ /**
75
+ * Compute the visible slice `[start, end)` so the active row stays in view,
76
+ * centering the cursor once the list grows past {@link MAX_VISIBLE}.
77
+ */
78
+ function visibleWindow(active, total) {
79
+ if (total <= MAX_VISIBLE)
80
+ return [0, total];
81
+ const half = Math.floor(MAX_VISIBLE / 2);
82
+ let start = Math.max(0, active - half);
83
+ let end = start + MAX_VISIBLE;
84
+ if (end > total) {
85
+ end = total;
86
+ start = end - MAX_VISIBLE;
87
+ }
88
+ return [start, end];
89
+ }
90
+ //# sourceMappingURL=ResourcePicker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResourcePicker.js","sourceRoot":"","sources":["../../src/components/ResourcePicker.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
2
+ /** Props for {@link SessionPicker}. */
3
+ export interface SessionPickerProps {
4
+ /** Seeds the filter box (e.g. text the user already typed). */
5
+ readonly initialQuery?: string;
6
+ /** Maximum sessions to fetch. Defaults to 50. */
7
+ readonly pageSize?: number;
8
+ /** Called with the chosen session on selection. */
9
+ readonly onSelect: (session: Session) => void;
10
+ /** Called when the user cancels (Esc / Ctrl+C). */
11
+ readonly onCancel: () => void;
12
+ }
13
+ /**
14
+ * Interactive picker for re-opening an existing session.
15
+ *
16
+ * Unlike agents, sessions are not search-indexed, so this composes
17
+ * {@link useSessionList} with client-side text filtering over the recent
18
+ * session list. The selected {@link Session} is returned via `onSelect`; use
19
+ * `session.metadata.id` to resume it.
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * render(
24
+ * <InkStigmerProvider client={client}>
25
+ * <SessionPicker onSelect={(s) => resume(s.metadata?.id)} onCancel={() => process.exit(0)} />
26
+ * </InkStigmerProvider>,
27
+ * );
28
+ * ```
29
+ */
30
+ export declare function SessionPicker({ initialQuery, pageSize, onSelect, onCancel }: SessionPickerProps): import("react/jsx-runtime").JSX.Element;
31
+ //# sourceMappingURL=SessionPicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionPicker.d.ts","sourceRoot":"","sources":["../../src/components/SessionPicker.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sDAAsD,CAAC;AAIpF,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC,+DAA+D;IAC/D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,mDAAmD;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC/B;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,EAAE,YAAY,EAAE,QAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,kBAAkB,2CA4BpG"}
@@ -0,0 +1,86 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useMemo, useState } from "react";
3
+ import { useSessionList } from "@stigmer/react";
4
+ import { ResourcePicker } from "./ResourcePicker.js";
5
+ const PENDING_SUBJECT = "Auto-created session";
6
+ /**
7
+ * Interactive picker for re-opening an existing session.
8
+ *
9
+ * Unlike agents, sessions are not search-indexed, so this composes
10
+ * {@link useSessionList} with client-side text filtering over the recent
11
+ * session list. The selected {@link Session} is returned via `onSelect`; use
12
+ * `session.metadata.id` to resume it.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * render(
17
+ * <InkStigmerProvider client={client}>
18
+ * <SessionPicker onSelect={(s) => resume(s.metadata?.id)} onCancel={() => process.exit(0)} />
19
+ * </InkStigmerProvider>,
20
+ * );
21
+ * ```
22
+ */
23
+ export function SessionPicker({ initialQuery, pageSize = 50, onSelect, onCancel }) {
24
+ const { sessions, isLoading, error } = useSessionList({ pageSize });
25
+ const [query, setQuery] = useState(initialQuery ?? "");
26
+ const filtered = useMemo(() => filterSessions(sessions, query), [sessions, query]);
27
+ const items = useMemo(() => filtered.map(toSessionItem), [filtered]);
28
+ const handleSelect = useCallback((item) => {
29
+ const found = sessions.find((s) => (s.metadata?.id ?? "") === item.id);
30
+ if (found !== undefined)
31
+ onSelect(found);
32
+ }, [sessions, onSelect]);
33
+ return (_jsx(ResourcePicker, { prompt: "Select a session", items: items, query: query, onQueryChange: setQuery, isLoading: isLoading, error: error, onSelect: handleSelect, onCancel: onCancel, emptyLabel: "no sessions found" }));
34
+ }
35
+ /** Case-insensitive filter over a session's subject, agent, and id. */
36
+ function filterSessions(sessions, query) {
37
+ const needle = query.trim().toLowerCase();
38
+ if (needle === "")
39
+ return sessions;
40
+ return sessions.filter((s) => sessionHaystack(s).includes(needle));
41
+ }
42
+ function sessionHaystack(session) {
43
+ return [sessionSubject(session), session.spec?.agentInstanceId ?? "", session.metadata?.id ?? ""]
44
+ .join(" ")
45
+ .toLowerCase();
46
+ }
47
+ /** Map a session to a picker row: subject title, agent subtitle, age meta. */
48
+ function toSessionItem(session) {
49
+ return {
50
+ id: session.metadata?.id ?? "",
51
+ title: sessionSubject(session),
52
+ subtitle: session.spec?.agentInstanceId ?? "",
53
+ meta: relativeTime(session.status?.audit?.specAudit?.createdAt),
54
+ };
55
+ }
56
+ // The session's display subject, suppressing the backend's auto-create
57
+ // sentinel (the async title activity replaces it shortly after creation).
58
+ function sessionSubject(session) {
59
+ const subject = session.spec?.subject ?? "";
60
+ if (subject !== "" && subject !== PENDING_SUBJECT)
61
+ return subject;
62
+ return session.metadata?.name !== undefined && session.metadata.name !== ""
63
+ ? session.metadata.name
64
+ : (session.metadata?.id ?? "(untitled session)");
65
+ }
66
+ // Coarse "N units ago" label from a protobuf Timestamp. Empty when unknown.
67
+ function relativeTime(ts) {
68
+ if (ts === undefined)
69
+ return "";
70
+ const then = Number(ts.seconds) * 1000;
71
+ if (!Number.isFinite(then) || then <= 0)
72
+ return "";
73
+ const seconds = Math.max(0, Math.floor((Date.now() - then) / 1000));
74
+ const units = [
75
+ [86400, "day"],
76
+ [3600, "hour"],
77
+ [60, "minute"],
78
+ ];
79
+ for (const [size, label] of units) {
80
+ const value = Math.floor(seconds / size);
81
+ if (value >= 1)
82
+ return `${value} ${label}${value === 1 ? "" : "s"} ago`;
83
+ }
84
+ return "just now";
85
+ }
86
+ //# sourceMappingURL=SessionPicker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionPicker.js","sourceRoot":"","sources":["../../src/components/SessionPicker.tsx"],"names":[],"mappings":"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"}
@@ -69,12 +69,26 @@ function describeResultView(view) {
69
69
  return truncate(view.text);
70
70
  case "json":
71
71
  return truncate(JSON.stringify(view.value));
72
+ case "outputRef": {
73
+ const size = view.sizeBytes ? ` (${formatBytes(view.sizeBytes)})` : "";
74
+ if (view.isImage)
75
+ return `image${size}: ${view.downloadUrl}`;
76
+ const preview = view.preview ? `${truncate(view.preview)}\n` : "";
77
+ return `${preview}full output${size}: ${view.downloadUrl}`;
78
+ }
72
79
  case "error":
73
80
  return view.message;
74
81
  case "empty":
75
82
  return null;
76
83
  }
77
84
  }
85
+ function formatBytes(n) {
86
+ if (n >= 1024 * 1024)
87
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
88
+ if (n >= 1024)
89
+ return `${(n / 1024).toFixed(1)} KB`;
90
+ return `${n} B`;
91
+ }
78
92
  function truncate(s, maxLines = 5) {
79
93
  const lines = s.split("\n");
80
94
  if (lines.length <= maxLines)
@@ -1 +1 @@
1
- {"version":3,"file":"ToolCallItem.js","sourceRoot":"","sources":["../../src/components/ToolCallItem.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,8DAA8D,CAAC;AAC9F,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAW9E,MAAM,gBAAgB,GAAuD;IAC3E,CAAC,cAAc,CAAC,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE;IACpE,CAAC,cAAc,CAAC,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE;IACrE,CAAC,cAAc,CAAC,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE;CACjE,CAAC;AAEF,8EAA8E;AAC9E,wEAAwE;AACxE,qEAAqE;AACrE,MAAM,UAAU,GAAsC;IACpD,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM;IAC5B,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO;IAC9B,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM;IAC5B,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ;IAChC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;IACzB,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ;IAC3B,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM;IACvB,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;IACzB,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY;IACnC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU;IAC5B,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO;IACxB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW;CACjC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,KAAK,EAAqB;IAC5E,MAAM,SAAS,GAAG,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;IAEvE,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,QAAQ,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAE9D,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,MAAC,GAAG,IAAC,GAAG,EAAE,CAAC,aACT,KAAC,IAAI,IAAC,KAAK,EAAE,SAAS,CAAC,KAAK,YAAG,SAAS,CAAC,MAAM,GAAQ,EACvD,KAAC,IAAI,cAAE,KAAK,GAAQ,EACnB,QAAQ,CAAC,MAAM,KAAK,cAAc,CAAC,iBAAiB,IAAI,CACvD,KAAC,IAAI,IAAC,QAAQ,8BAAe,CAC9B,IACG,EACL,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CACvB,QAAQ,IAAI,CACV,KAAC,GAAG,IAAC,WAAW,EAAE,CAAC,YACjB,KAAC,IAAI,IAAC,KAAK,EAAC,KAAK,EAAC,IAAI,EAAC,cAAc,YAClC,IAAI,CAAC,OAAO,GACR,GACH,CACP,CACF,CAAC,CAAC,CAAC,CACF,UAAU,IAAI,CACZ,KAAC,GAAG,IAAC,WAAW,EAAE,CAAC,YACjB,KAAC,IAAI,IAAC,QAAQ,QAAC,IAAI,EAAC,cAAc,YAC/B,UAAU,GACN,GACH,CACP,CACF,IACG,CACP,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,QAAkB,EAAE,IAAc;IACnD,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;QAC3B,OAAO,GAAG,QAAQ,CAAC,aAAa,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;IACtD,CAAC;IACD,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC;AAC3C,CAAC;AAED,gFAAgF;AAChF,SAAS,kBAAkB,CAAC,IAAoB;IAC9C,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,KAAK,GACT,IAAI,CAAC,UAAU,KAAK,SAAS,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS;gBAC9D,CAAC,CAAC,MAAM,IAAI,CAAC,UAAU,IAAI,CAAC,KAAK,IAAI,CAAC,YAAY,IAAI,CAAC,GAAG;gBAC1D,CAAC,CAAC,EAAE,CAAC;YACT,OAAO,GAAG,IAAI,CAAC,IAAI,GAAG,KAAK,EAAE,CAAC;QAChC,CAAC;QACD,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,IAAI,GACR,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACvF,OAAO,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC;QACrD,CAAC;QACD,KAAK,QAAQ;YACX,OAAO,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;QACnE,KAAK,MAAM;YACT,OAAO,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QAChE,KAAK,MAAM;YACT,OAAO,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC;QAC3B,KAAK,eAAe;YAClB,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7E,KAAK,MAAM;YACT,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,KAAK,MAAM;YACT,OAAO,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9C,KAAK,OAAO;YACV,OAAO,IAAI,CAAC,OAAO,CAAC;QACtB,KAAK,OAAO;YACV,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS,EAAE,QAAQ,GAAG,CAAC;IACvC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ;QAAE,OAAO,CAAC,CAAC;IACvC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,UAAU,KAAK,CAAC,MAAM,GAAG,QAAQ,cAAc,CAAC;AAC/F,CAAC"}
1
+ {"version":3,"file":"ToolCallItem.js","sourceRoot":"","sources":["../../src/components/ToolCallItem.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,8DAA8D,CAAC;AAC9F,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAW9E,MAAM,gBAAgB,GAAuD;IAC3E,CAAC,cAAc,CAAC,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE;IACpE,CAAC,cAAc,CAAC,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE;IACrE,CAAC,cAAc,CAAC,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE;CACjE,CAAC;AAEF,8EAA8E;AAC9E,wEAAwE;AACxE,qEAAqE;AACrE,MAAM,UAAU,GAAsC;IACpD,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM;IAC5B,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO;IAC9B,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM;IAC5B,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ;IAChC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;IACzB,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ;IAC3B,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM;IACvB,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;IACzB,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY;IACnC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU;IAC5B,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO;IACxB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW;CACjC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,KAAK,EAAqB;IAC5E,MAAM,SAAS,GAAG,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;IAEvE,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,QAAQ,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAE9D,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,MAAC,GAAG,IAAC,GAAG,EAAE,CAAC,aACT,KAAC,IAAI,IAAC,KAAK,EAAE,SAAS,CAAC,KAAK,YAAG,SAAS,CAAC,MAAM,GAAQ,EACvD,KAAC,IAAI,cAAE,KAAK,GAAQ,EACnB,QAAQ,CAAC,MAAM,KAAK,cAAc,CAAC,iBAAiB,IAAI,CACvD,KAAC,IAAI,IAAC,QAAQ,8BAAe,CAC9B,IACG,EACL,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CACvB,QAAQ,IAAI,CACV,KAAC,GAAG,IAAC,WAAW,EAAE,CAAC,YACjB,KAAC,IAAI,IAAC,KAAK,EAAC,KAAK,EAAC,IAAI,EAAC,cAAc,YAClC,IAAI,CAAC,OAAO,GACR,GACH,CACP,CACF,CAAC,CAAC,CAAC,CACF,UAAU,IAAI,CACZ,KAAC,GAAG,IAAC,WAAW,EAAE,CAAC,YACjB,KAAC,IAAI,IAAC,QAAQ,QAAC,IAAI,EAAC,cAAc,YAC/B,UAAU,GACN,GACH,CACP,CACF,IACG,CACP,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,QAAkB,EAAE,IAAc;IACnD,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;QAC3B,OAAO,GAAG,QAAQ,CAAC,aAAa,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;IACtD,CAAC;IACD,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC;AAC3C,CAAC;AAED,gFAAgF;AAChF,SAAS,kBAAkB,CAAC,IAAoB;IAC9C,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,KAAK,GACT,IAAI,CAAC,UAAU,KAAK,SAAS,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS;gBAC9D,CAAC,CAAC,MAAM,IAAI,CAAC,UAAU,IAAI,CAAC,KAAK,IAAI,CAAC,YAAY,IAAI,CAAC,GAAG;gBAC1D,CAAC,CAAC,EAAE,CAAC;YACT,OAAO,GAAG,IAAI,CAAC,IAAI,GAAG,KAAK,EAAE,CAAC;QAChC,CAAC;QACD,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,IAAI,GACR,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACvF,OAAO,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC;QACrD,CAAC;QACD,KAAK,QAAQ;YACX,OAAO,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;QACnE,KAAK,MAAM;YACT,OAAO,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QAChE,KAAK,MAAM;YACT,OAAO,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC;QAC3B,KAAK,eAAe;YAClB,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7E,KAAK,MAAM;YACT,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,KAAK,MAAM;YACT,OAAO,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9C,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACvE,IAAI,IAAI,CAAC,OAAO;gBAAE,OAAO,QAAQ,IAAI,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;YAC7D,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAClE,OAAO,GAAG,OAAO,cAAc,IAAI,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;QAC7D,CAAC;QACD,KAAK,OAAO;YACV,OAAO,IAAI,CAAC,OAAO,CAAC;QACtB,KAAK,OAAO;YACV,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,CAAS;IAC5B,IAAI,CAAC,IAAI,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IACpE,IAAI,CAAC,IAAI,IAAI;QAAE,OAAO,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IACpD,OAAO,GAAG,CAAC,IAAI,CAAC;AAClB,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS,EAAE,QAAQ,GAAG,CAAC;IACvC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ;QAAE,OAAO,CAAC,CAAC;IACvC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,UAAU,KAAK,CAAC,MAAM,GAAG,QAAQ,cAAc,CAAC;AAC/F,CAAC"}
package/index.d.ts CHANGED
@@ -12,6 +12,9 @@ export { ExecutionProgress, type ExecutionProgressProps } from "./components/Exe
12
12
  export { FollowUpInput, type FollowUpInputProps } from "./components/FollowUpInput.js";
13
13
  export { UsageWidget, type UsageWidgetProps } from "./components/UsageWidget.js";
14
14
  export { ContextGauge, type ContextGaugeProps } from "./components/ContextGauge.js";
15
+ export { ResourcePicker, type ResourcePickerProps, type PickerItem } from "./components/ResourcePicker.js";
16
+ export { AgentPicker, type AgentPickerProps } from "./components/AgentPicker.js";
17
+ export { SessionPicker, type SessionPickerProps } from "./components/SessionPicker.js";
15
18
  export { SessionView, type SessionViewProps, type InteractionMode } from "./app/SessionView.js";
16
19
  export { SessionApp, type SessionAppProps } from "./app/SessionApp.js";
17
20
  //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAGjF,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AACnG,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGpF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAChG,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAGjF,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AACnG,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGpF,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,KAAK,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC3G,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAGvF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAChG,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC"}
package/index.js CHANGED
@@ -16,6 +16,10 @@ export { ExecutionProgress } from "./components/ExecutionProgress.js";
16
16
  export { FollowUpInput } from "./components/FollowUpInput.js";
17
17
  export { UsageWidget } from "./components/UsageWidget.js";
18
18
  export { ContextGauge } from "./components/ContextGauge.js";
19
+ // Pickers (interactive search-and-select)
20
+ export { ResourcePicker } from "./components/ResourcePicker.js";
21
+ export { AgentPicker } from "./components/AgentPicker.js";
22
+ export { SessionPicker } from "./components/SessionPicker.js";
19
23
  // Composed views
20
24
  export { SessionView } from "./app/SessionView.js";
21
25
  export { SessionApp } from "./app/SessionApp.js";
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,WAAW;AACX,OAAO,EAAE,kBAAkB,EAAgC,MAAM,eAAe,CAAC;AAEjF,iDAAiD;AACjD,OAAO,EACL,gBAAgB,EAChB,mBAAmB,GAEpB,MAAM,mBAAmB,CAAC;AAE3B,WAAW;AACX,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,aAAa;AACb,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAsB,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,cAAc,EAA4B,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAA+B,MAAM,mCAAmC,CAAC;AACnG,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,WAAW,EAAyB,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AAEpF,iBAAiB;AACjB,OAAO,EAAE,WAAW,EAA+C,MAAM,sBAAsB,CAAC;AAChG,OAAO,EAAE,UAAU,EAAwB,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,WAAW;AACX,OAAO,EAAE,kBAAkB,EAAgC,MAAM,eAAe,CAAC;AAEjF,iDAAiD;AACjD,OAAO,EACL,gBAAgB,EAChB,mBAAmB,GAEpB,MAAM,mBAAmB,CAAC;AAE3B,WAAW;AACX,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,aAAa;AACb,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAsB,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,cAAc,EAA4B,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAA+B,MAAM,mCAAmC,CAAC;AACnG,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,WAAW,EAAyB,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AAEpF,0CAA0C;AAC1C,OAAO,EAAE,cAAc,EAA6C,MAAM,gCAAgC,CAAC;AAC3G,OAAO,EAAE,WAAW,EAAyB,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AAEvF,iBAAiB;AACjB,OAAO,EAAE,WAAW,EAA+C,MAAM,sBAAsB,CAAC;AAChG,OAAO,EAAE,UAAU,EAAwB,MAAM,qBAAqB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/ink",
3
- "version": "3.0.8-dev.20260613074252",
3
+ "version": "3.0.8",
4
4
  "description": "Ink (React for terminals) components for rendering Stigmer agent sessions in the terminal",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@connectrpc/connect-node": "^2.1.1",
38
- "@stigmer/react": "3.0.8-dev.20260613074252",
38
+ "@stigmer/react": "3.0.8",
39
39
  "ink-spinner": "^5.0.0",
40
40
  "ink-text-input": "^6.0.0",
41
41
  "marked": "^15.0.7",
@@ -43,8 +43,8 @@
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@bufbuild/protobuf": "^2.0.0",
46
- "@stigmer/protos": "3.0.8-dev.20260613074252",
47
- "@stigmer/sdk": "3.0.8-dev.20260613074252",
46
+ "@stigmer/protos": "3.0.8",
47
+ "@stigmer/sdk": "3.0.8",
48
48
  "ink": "^7.0.0",
49
49
  "react": ">=19.0.0"
50
50
  }
@@ -0,0 +1,108 @@
1
+ import React from "react";
2
+ import { describe, it, expect, vi } from "vitest";
3
+ import { render } from "ink-testing-library";
4
+ import { create } from "@bufbuild/protobuf";
5
+ import type { Stigmer } from "@stigmer/sdk";
6
+ import { SearchResultSchema } from "@stigmer/protos/ai/stigmer/search/v1/io_pb";
7
+ import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
8
+ import { InkStigmerProvider } from "../provider.js";
9
+ import { AgentPicker } from "../components/AgentPicker.js";
10
+ import { SessionPicker } from "../components/SessionPicker.js";
11
+
12
+ const KEY = { enter: "\r" } as const;
13
+
14
+ // Allow the hooks' async fetch + a re-render to settle.
15
+ function settle(): Promise<void> {
16
+ return new Promise((resolve) => setTimeout(resolve, 60));
17
+ }
18
+
19
+ function agentResult(id: string, slug: string, description: string) {
20
+ return create(SearchResultSchema, { id, qualifiedSlug: slug, description });
21
+ }
22
+
23
+ function session(id: string, subject: string, agentInstanceId: string) {
24
+ return create(SessionSchema, {
25
+ metadata: { id },
26
+ spec: { subject, agentInstanceId },
27
+ });
28
+ }
29
+
30
+ // Minimal Stigmer stub exposing only the methods the picker hooks call.
31
+ function fakeClient(overrides: Record<string, unknown>): Stigmer {
32
+ return overrides as unknown as Stigmer;
33
+ }
34
+
35
+ describe("AgentPicker", () => {
36
+ it("renders search results and returns the chosen agent on Enter", async () => {
37
+ const results = [
38
+ agentResult("agt_1", "acme/alpha", "first"),
39
+ agentResult("agt_2", "acme/beta", "second"),
40
+ ];
41
+ const client = fakeClient({
42
+ agent: { list: vi.fn(async () => ({ entries: results, totalPages: 1, totalCount: 2 })) },
43
+ });
44
+ const onSelect = vi.fn();
45
+
46
+ const { lastFrame, stdin } = render(
47
+ <InkStigmerProvider client={client}>
48
+ <AgentPicker org="acme" onSelect={onSelect} onCancel={vi.fn()} />
49
+ </InkStigmerProvider>,
50
+ );
51
+
52
+ await settle();
53
+ expect(lastFrame() ?? "").toContain("acme/alpha");
54
+ expect(lastFrame() ?? "").toContain("first");
55
+
56
+ stdin.write(KEY.enter);
57
+ await settle();
58
+ expect(onSelect).toHaveBeenCalledTimes(1);
59
+ expect(onSelect.mock.calls[0][0].id).toBe("agt_1");
60
+ });
61
+
62
+ it("seeds the search box from initialQuery", async () => {
63
+ const list = vi.fn(async () => ({ entries: [], totalPages: 0, totalCount: 0 }));
64
+ const client = fakeClient({ agent: { list } });
65
+
66
+ const { lastFrame } = render(
67
+ <InkStigmerProvider client={client}>
68
+ <AgentPicker org="acme" initialQuery="deploy" onSelect={vi.fn()} onCancel={vi.fn()} />
69
+ </InkStigmerProvider>,
70
+ );
71
+
72
+ await settle();
73
+ // The picker pre-fills the input with the unresolved query the user
74
+ // already typed; useAgentSearch then re-searches it (debounced).
75
+ expect(lastFrame() ?? "").toContain("deploy");
76
+ });
77
+ });
78
+
79
+ describe("SessionPicker", () => {
80
+ it("client-side filters the session list and returns the chosen session", async () => {
81
+ const sessions = [
82
+ session("ses_1", "deploy staging", "ai_1"),
83
+ session("ses_2", "review PR", "ai_2"),
84
+ session("ses_3", "deploy prod", "ai_3"),
85
+ ];
86
+ const client = fakeClient({
87
+ session: { list: vi.fn(async () => ({ entries: sessions })) },
88
+ });
89
+ const onSelect = vi.fn();
90
+
91
+ const { lastFrame, stdin } = render(
92
+ <InkStigmerProvider client={client}>
93
+ <SessionPicker initialQuery="deploy" onSelect={onSelect} onCancel={vi.fn()} />
94
+ </InkStigmerProvider>,
95
+ );
96
+
97
+ await settle();
98
+ const frame = lastFrame() ?? "";
99
+ expect(frame).toContain("deploy staging");
100
+ expect(frame).toContain("deploy prod");
101
+ expect(frame).not.toContain("review PR");
102
+
103
+ stdin.write(KEY.enter);
104
+ await settle();
105
+ expect(onSelect).toHaveBeenCalledTimes(1);
106
+ expect(onSelect.mock.calls[0][0].metadata.id).toBe("ses_1");
107
+ });
108
+ });
@@ -0,0 +1,176 @@
1
+ import React from "react";
2
+ import { describe, it, expect, vi } from "vitest";
3
+ import { render } from "ink-testing-library";
4
+ import { type PickerItem, ResourcePicker } from "../components/ResourcePicker.js";
5
+
6
+ // Key escape sequences understood by ink's input parser.
7
+ const KEY = {
8
+ up: "\u001B[A",
9
+ down: "\u001B[B",
10
+ enter: "\r",
11
+ escape: "\u001B",
12
+ } as const;
13
+
14
+ const ITEMS: PickerItem[] = [
15
+ { id: "agt_1", title: "acme/alpha", subtitle: "first agent" },
16
+ { id: "agt_2", title: "acme/beta", subtitle: "second agent" },
17
+ { id: "agt_3", title: "acme/gamma", subtitle: "third agent", meta: "2 hours ago" },
18
+ ];
19
+
20
+ // Ink processes stdin asynchronously; yield a macrotask so the frame settles.
21
+ function tick(): Promise<void> {
22
+ return new Promise((resolve) => setTimeout(resolve, 20));
23
+ }
24
+
25
+ function noop(): void {}
26
+
27
+ describe("ResourcePicker", () => {
28
+ it("renders the prompt, all items, and the result count", () => {
29
+ const { lastFrame } = render(
30
+ <ResourcePicker
31
+ prompt="Select an agent"
32
+ items={ITEMS}
33
+ query=""
34
+ onQueryChange={noop}
35
+ onSelect={noop}
36
+ onCancel={noop}
37
+ />,
38
+ );
39
+ const out = lastFrame() ?? "";
40
+ expect(out).toContain("Select an agent");
41
+ expect(out).toContain("acme/alpha");
42
+ expect(out).toContain("acme/gamma");
43
+ expect(out).toContain("first agent");
44
+ expect(out).toContain("2 hours ago");
45
+ expect(out).toContain("3 results");
46
+ });
47
+
48
+ it("marks the first item active by default", () => {
49
+ const { lastFrame } = render(
50
+ <ResourcePicker items={ITEMS} query="" onQueryChange={noop} onSelect={noop} onCancel={noop} />,
51
+ );
52
+ expect(lastFrame() ?? "").toContain("❯ acme/alpha");
53
+ });
54
+
55
+ it("selects the active item on Enter", async () => {
56
+ const onSelect = vi.fn();
57
+ const { stdin } = render(
58
+ <ResourcePicker items={ITEMS} query="" onQueryChange={noop} onSelect={onSelect} onCancel={noop} />,
59
+ );
60
+ stdin.write(KEY.enter);
61
+ await tick();
62
+ expect(onSelect).toHaveBeenCalledTimes(1);
63
+ expect(onSelect).toHaveBeenCalledWith(ITEMS[0]);
64
+ });
65
+
66
+ it("moves the cursor down and selects the new active item", async () => {
67
+ const onSelect = vi.fn();
68
+ const { stdin } = render(
69
+ <ResourcePicker items={ITEMS} query="" onQueryChange={noop} onSelect={onSelect} onCancel={noop} />,
70
+ );
71
+ stdin.write(KEY.down);
72
+ await tick();
73
+ stdin.write(KEY.down);
74
+ await tick();
75
+ stdin.write(KEY.enter);
76
+ await tick();
77
+ expect(onSelect).toHaveBeenCalledWith(ITEMS[2]);
78
+ });
79
+
80
+ it("clamps cursor movement at the list boundaries", async () => {
81
+ const onSelect = vi.fn();
82
+ const { stdin } = render(
83
+ <ResourcePicker items={ITEMS} query="" onQueryChange={noop} onSelect={onSelect} onCancel={noop} />,
84
+ );
85
+ // Up at the top stays on the first row.
86
+ stdin.write(KEY.up);
87
+ await tick();
88
+ stdin.write(KEY.enter);
89
+ await tick();
90
+ expect(onSelect).toHaveBeenLastCalledWith(ITEMS[0]);
91
+ // Past the bottom stays on the last row.
92
+ stdin.write(KEY.down);
93
+ await tick();
94
+ stdin.write(KEY.down);
95
+ await tick();
96
+ stdin.write(KEY.down);
97
+ await tick();
98
+ stdin.write(KEY.enter);
99
+ await tick();
100
+ expect(onSelect).toHaveBeenLastCalledWith(ITEMS[2]);
101
+ });
102
+
103
+ it("cancels on Escape without selecting", async () => {
104
+ const onSelect = vi.fn();
105
+ const onCancel = vi.fn();
106
+ const { stdin } = render(
107
+ <ResourcePicker items={ITEMS} query="" onQueryChange={noop} onSelect={onSelect} onCancel={onCancel} />,
108
+ );
109
+ stdin.write(KEY.escape);
110
+ await tick();
111
+ expect(onCancel).toHaveBeenCalledTimes(1);
112
+ expect(onSelect).not.toHaveBeenCalled();
113
+ });
114
+
115
+ it("forwards typed characters to onQueryChange", async () => {
116
+ const onQueryChange = vi.fn();
117
+ const { stdin } = render(
118
+ <ResourcePicker items={ITEMS} query="" onQueryChange={onQueryChange} onSelect={noop} onCancel={noop} />,
119
+ );
120
+ stdin.write("a");
121
+ await tick();
122
+ expect(onQueryChange).toHaveBeenCalledWith("a");
123
+ });
124
+
125
+ it("does not select when Enter is pressed on an empty list", async () => {
126
+ const onSelect = vi.fn();
127
+ const { stdin, lastFrame } = render(
128
+ <ResourcePicker items={[]} query="zzz" onQueryChange={noop} onSelect={onSelect} onCancel={noop} />,
129
+ );
130
+ expect(lastFrame() ?? "").toContain("no results");
131
+ stdin.write(KEY.enter);
132
+ await tick();
133
+ expect(onSelect).not.toHaveBeenCalled();
134
+ });
135
+
136
+ it("shows a loading state while searching", () => {
137
+ const { lastFrame } = render(
138
+ <ResourcePicker items={[]} query="x" onQueryChange={noop} onSelect={noop} onCancel={noop} isLoading />,
139
+ );
140
+ expect(lastFrame() ?? "").toContain("searching...");
141
+ });
142
+
143
+ it("shows an error state in place of the list", () => {
144
+ const { lastFrame } = render(
145
+ <ResourcePicker
146
+ items={[]}
147
+ query="x"
148
+ onQueryChange={noop}
149
+ onSelect={noop}
150
+ onCancel={noop}
151
+ error={new Error("backend unreachable")}
152
+ />,
153
+ );
154
+ expect(lastFrame() ?? "").toContain("Error: backend unreachable");
155
+ });
156
+
157
+ it("scrolls the visible window to keep the active row in view", async () => {
158
+ const many: PickerItem[] = Array.from({ length: 14 }, (_, i) => ({
159
+ id: `id_${i}`,
160
+ title: `item-${i}`,
161
+ }));
162
+ const { stdin, lastFrame } = render(
163
+ <ResourcePicker items={many} query="" onQueryChange={noop} onSelect={noop} onCancel={noop} />,
164
+ );
165
+ // Initially the top of the list is visible, the tail is not.
166
+ expect(lastFrame() ?? "").toContain("item-0");
167
+ expect(lastFrame() ?? "").not.toContain("item-13");
168
+ // Drive the cursor to the bottom; the window should scroll to reveal it.
169
+ for (let i = 0; i < 13; i++) {
170
+ stdin.write(KEY.down);
171
+ await tick();
172
+ }
173
+ expect(lastFrame() ?? "").toContain("item-13");
174
+ expect(lastFrame() ?? "").not.toContain("item-0");
175
+ }, 20000);
176
+ });
@@ -0,0 +1,84 @@
1
+ import React, { useCallback, useEffect, useMemo } from "react";
2
+ import type { SearchResult } from "@stigmer/protos/ai/stigmer/search/v1/io_pb";
3
+ import { useAgentSearch } from "@stigmer/react";
4
+ import { type PickerItem, ResourcePicker } from "./ResourcePicker.js";
5
+
6
+ /** Props for {@link AgentPicker}. */
7
+ export interface AgentPickerProps {
8
+ /** Organization to search within. */
9
+ readonly org: string;
10
+ /** Seeds the search box (e.g. an unresolved slug the user already typed). */
11
+ readonly initialQuery?: string;
12
+ /** `"org"` (default) searches the org; `"all"` includes public agents. */
13
+ readonly scope?: "org" | "all";
14
+ /** Called with the chosen agent's search result on selection. */
15
+ readonly onSelect: (agent: SearchResult) => void;
16
+ /** Called when the user cancels (Esc / Ctrl+C). */
17
+ readonly onCancel: () => void;
18
+ }
19
+
20
+ /** Max description length shown as the row subtitle before truncation. */
21
+ const SUBTITLE_MAX = 60;
22
+
23
+ /**
24
+ * Interactive picker for choosing an agent by name.
25
+ *
26
+ * Composes the {@link useAgentSearch} data hook with {@link ResourcePicker}:
27
+ * the user types, results stream in (debounced by the hook), and the selected
28
+ * agent's {@link SearchResult} is returned via `onSelect`. Use the `id` field of
29
+ * that result to resolve and run the agent.
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * render(
34
+ * <InkStigmerProvider client={client}>
35
+ * <AgentPicker org="acme" onSelect={(a) => run(a.id)} onCancel={() => process.exit(0)} />
36
+ * </InkStigmerProvider>,
37
+ * );
38
+ * ```
39
+ */
40
+ export function AgentPicker({ org, initialQuery, scope, onSelect, onCancel }: AgentPickerProps) {
41
+ const { results, isLoading, error, query, setQuery } = useAgentSearch(org, { scope });
42
+
43
+ // Seed the typed-but-unresolved query once on mount so the user picks up
44
+ // where the smart-resolution attempt left off.
45
+ useEffect(() => {
46
+ if (initialQuery !== undefined && initialQuery !== "") setQuery(initialQuery);
47
+ // Mount-only: re-seeding on every render would fight the user's typing.
48
+ // eslint-disable-next-line react-hooks/exhaustive-deps
49
+ }, []);
50
+
51
+ const items = useMemo<PickerItem[]>(() => results.map(toAgentItem), [results]);
52
+
53
+ const handleSelect = useCallback(
54
+ (item: PickerItem) => {
55
+ const found = results.find((r) => r.id === item.id);
56
+ if (found !== undefined) onSelect(found);
57
+ },
58
+ [results, onSelect],
59
+ );
60
+
61
+ return (
62
+ <ResourcePicker
63
+ prompt="Select an agent"
64
+ items={items}
65
+ query={query}
66
+ onQueryChange={setQuery}
67
+ isLoading={isLoading}
68
+ error={error}
69
+ onSelect={handleSelect}
70
+ onCancel={onCancel}
71
+ emptyLabel="no agents found"
72
+ />
73
+ );
74
+ }
75
+
76
+ /** Map a search result to a picker row: org/slug title, description subtitle. */
77
+ function toAgentItem(result: SearchResult): PickerItem {
78
+ const title = result.qualifiedSlug !== "" ? result.qualifiedSlug : `${result.org}/${result.slug}`;
79
+ return { id: result.id, title, subtitle: truncate(result.description, SUBTITLE_MAX) };
80
+ }
81
+
82
+ function truncate(text: string, max: number): string {
83
+ return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
84
+ }
@@ -0,0 +1,187 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import TextInput from "ink-text-input";
4
+
5
+ /**
6
+ * A single selectable entry in a {@link ResourcePicker}.
7
+ *
8
+ * Callers map their domain objects (agents, sessions, ...) into this shape and
9
+ * receive the chosen item back via `onSelect`. The picker is domain-agnostic:
10
+ * it knows only how to render and navigate these rows.
11
+ */
12
+ export interface PickerItem {
13
+ /** Opaque identity returned to the caller on selection. */
14
+ readonly id: string;
15
+ /** Primary display text, rendered bold when active (e.g. "acme/deploy"). */
16
+ readonly title: string;
17
+ /** Secondary text, rendered dimmed after the title (e.g. a description). */
18
+ readonly subtitle?: string;
19
+ /** Right-aligned metadata, rendered dimmed (e.g. "2 hours ago"). */
20
+ readonly meta?: string;
21
+ }
22
+
23
+ /** Props for {@link ResourcePicker}. */
24
+ export interface ResourcePickerProps {
25
+ /** The current result rows to choose from (owned by the caller). */
26
+ readonly items: readonly PickerItem[];
27
+ /** Current query text (controlled — the caller owns search state). */
28
+ readonly query: string;
29
+ /** Called when the query text changes; the caller re-runs its search. */
30
+ readonly onQueryChange: (query: string) => void;
31
+ /** Called with the chosen item when the user presses Enter. */
32
+ readonly onSelect: (item: PickerItem) => void;
33
+ /** Called when the user cancels (Esc or Ctrl+C). */
34
+ readonly onCancel: () => void;
35
+ /** `true` while a search request is in flight. */
36
+ readonly isLoading?: boolean;
37
+ /** Error from the last failed search, shown in place of the list. */
38
+ readonly error?: Error | null;
39
+ /** Label shown before the input (e.g. "Select an agent"). */
40
+ readonly prompt?: string;
41
+ /** Placeholder shown in the empty input. */
42
+ readonly placeholder?: string;
43
+ /** Label shown when a completed search returns no rows. */
44
+ readonly emptyLabel?: string;
45
+ }
46
+
47
+ /** Maximum rows shown at once; the list scrolls a window around the cursor. */
48
+ const MAX_VISIBLE = 10;
49
+
50
+ /**
51
+ * Generic, interactive search-and-select list for the terminal.
52
+ *
53
+ * Renders a text input above a scrollable result list. The user types to
54
+ * filter (the caller performs the actual search via `onQueryChange`), navigates
55
+ * with the arrow keys, selects with Enter, and cancels with Esc. It holds no
56
+ * data of its own — pass `items`/`query`/`isLoading` from a data hook such as
57
+ * `useAgentSearch` or `useSessionList`.
58
+ *
59
+ * This is the reusable organism behind {@link AgentPicker} and
60
+ * {@link SessionPicker}; platform builders can compose it with any search
61
+ * function to pick any resource.
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * const { results, isLoading, query, setQuery } = useAgentSearch("acme");
66
+ * const items = results.map((r) => ({ id: r.id, title: r.qualifiedSlug }));
67
+ * render(
68
+ * <ResourcePicker
69
+ * prompt="Select an agent"
70
+ * items={items}
71
+ * query={query}
72
+ * onQueryChange={setQuery}
73
+ * isLoading={isLoading}
74
+ * onSelect={(item) => console.log(item.id)}
75
+ * onCancel={() => process.exit(0)}
76
+ * />,
77
+ * );
78
+ * ```
79
+ */
80
+ export function ResourcePicker({
81
+ items,
82
+ query,
83
+ onQueryChange,
84
+ onSelect,
85
+ onCancel,
86
+ isLoading = false,
87
+ error = null,
88
+ prompt = "Search",
89
+ placeholder = "type to search...",
90
+ emptyLabel = "no results",
91
+ }: ResourcePickerProps) {
92
+ const [cursor, setCursor] = useState(0);
93
+
94
+ // Reset the cursor to the top whenever the query changes — the result set is
95
+ // about to be replaced, so a stale cursor would point at the wrong row.
96
+ useEffect(() => {
97
+ setCursor(0);
98
+ }, [query]);
99
+
100
+ const active = items.length === 0 ? 0 : Math.min(cursor, items.length - 1);
101
+
102
+ useInput((input, key) => {
103
+ if (key.escape || (key.ctrl && input === "c")) {
104
+ onCancel();
105
+ return;
106
+ }
107
+ if (key.return) {
108
+ if (items.length > 0) onSelect(items[active]);
109
+ return;
110
+ }
111
+ if (key.upArrow) {
112
+ setCursor((c) => Math.max(0, Math.min(c, items.length - 1) - 1));
113
+ return;
114
+ }
115
+ if (key.downArrow) {
116
+ setCursor((c) => Math.min(items.length - 1, c + 1));
117
+ }
118
+ });
119
+
120
+ const [start, end] = visibleWindow(active, items.length);
121
+
122
+ return (
123
+ <Box flexDirection="column">
124
+ <Box gap={1}>
125
+ <Text color="cyan" bold>
126
+ ?
127
+ </Text>
128
+ <Text bold>{prompt}:</Text>
129
+ <TextInput value={query} onChange={onQueryChange} placeholder={placeholder} />
130
+ </Box>
131
+
132
+ <Box marginTop={1} flexDirection="column">
133
+ {error !== null ? (
134
+ <Text color="red">{` Error: ${error.message}`}</Text>
135
+ ) : isLoading ? (
136
+ <Text dimColor> searching...</Text>
137
+ ) : items.length === 0 ? (
138
+ <Text dimColor>{` ${emptyLabel}`}</Text>
139
+ ) : (
140
+ <>
141
+ {items.slice(start, end).map((item, i) => (
142
+ <PickerRow key={item.id} item={item} isActive={start + i === active} />
143
+ ))}
144
+ <Box marginTop={1}>
145
+ <Text dimColor>{` ${countLabel(items.length)} | up/down: navigate | enter: select | esc: cancel`}</Text>
146
+ </Box>
147
+ </>
148
+ )}
149
+ </Box>
150
+ </Box>
151
+ );
152
+ }
153
+
154
+ /** One result row: a cursor glyph, the title, then optional subtitle/meta. */
155
+ function PickerRow({ item, isActive }: { readonly item: PickerItem; readonly isActive: boolean }) {
156
+ return (
157
+ <Box>
158
+ <Text color={isActive ? "cyan" : undefined}>{isActive ? "❯ " : " "}</Text>
159
+ <Text color={isActive ? "cyan" : undefined} bold={isActive}>
160
+ {item.title}
161
+ </Text>
162
+ {item.subtitle !== undefined && item.subtitle !== "" ? <Text dimColor>{` ${item.subtitle}`}</Text> : null}
163
+ {item.meta !== undefined && item.meta !== "" ? <Text dimColor>{` ${item.meta}`}</Text> : null}
164
+ </Box>
165
+ );
166
+ }
167
+
168
+ /** "1 result" / "N results" — matches the Go picker's footer wording. */
169
+ function countLabel(count: number): string {
170
+ return `${count} result${count === 1 ? "" : "s"}`;
171
+ }
172
+
173
+ /**
174
+ * Compute the visible slice `[start, end)` so the active row stays in view,
175
+ * centering the cursor once the list grows past {@link MAX_VISIBLE}.
176
+ */
177
+ function visibleWindow(active: number, total: number): readonly [number, number] {
178
+ if (total <= MAX_VISIBLE) return [0, total];
179
+ const half = Math.floor(MAX_VISIBLE / 2);
180
+ let start = Math.max(0, active - half);
181
+ let end = start + MAX_VISIBLE;
182
+ if (end > total) {
183
+ end = total;
184
+ start = end - MAX_VISIBLE;
185
+ }
186
+ return [start, end];
187
+ }
@@ -0,0 +1,117 @@
1
+ import React, { useCallback, useMemo, useState } from "react";
2
+ import type { Timestamp } from "@bufbuild/protobuf/wkt";
3
+ import type { Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
4
+ import { useSessionList } from "@stigmer/react";
5
+ import { type PickerItem, ResourcePicker } from "./ResourcePicker.js";
6
+
7
+ /** Props for {@link SessionPicker}. */
8
+ export interface SessionPickerProps {
9
+ /** Seeds the filter box (e.g. text the user already typed). */
10
+ readonly initialQuery?: string;
11
+ /** Maximum sessions to fetch. Defaults to 50. */
12
+ readonly pageSize?: number;
13
+ /** Called with the chosen session on selection. */
14
+ readonly onSelect: (session: Session) => void;
15
+ /** Called when the user cancels (Esc / Ctrl+C). */
16
+ readonly onCancel: () => void;
17
+ }
18
+
19
+ const PENDING_SUBJECT = "Auto-created session";
20
+
21
+ /**
22
+ * Interactive picker for re-opening an existing session.
23
+ *
24
+ * Unlike agents, sessions are not search-indexed, so this composes
25
+ * {@link useSessionList} with client-side text filtering over the recent
26
+ * session list. The selected {@link Session} is returned via `onSelect`; use
27
+ * `session.metadata.id` to resume it.
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * render(
32
+ * <InkStigmerProvider client={client}>
33
+ * <SessionPicker onSelect={(s) => resume(s.metadata?.id)} onCancel={() => process.exit(0)} />
34
+ * </InkStigmerProvider>,
35
+ * );
36
+ * ```
37
+ */
38
+ export function SessionPicker({ initialQuery, pageSize = 50, onSelect, onCancel }: SessionPickerProps) {
39
+ const { sessions, isLoading, error } = useSessionList({ pageSize });
40
+ const [query, setQuery] = useState(initialQuery ?? "");
41
+
42
+ const filtered = useMemo(() => filterSessions(sessions, query), [sessions, query]);
43
+ const items = useMemo<PickerItem[]>(() => filtered.map(toSessionItem), [filtered]);
44
+
45
+ const handleSelect = useCallback(
46
+ (item: PickerItem) => {
47
+ const found = sessions.find((s) => (s.metadata?.id ?? "") === item.id);
48
+ if (found !== undefined) onSelect(found);
49
+ },
50
+ [sessions, onSelect],
51
+ );
52
+
53
+ return (
54
+ <ResourcePicker
55
+ prompt="Select a session"
56
+ items={items}
57
+ query={query}
58
+ onQueryChange={setQuery}
59
+ isLoading={isLoading}
60
+ error={error}
61
+ onSelect={handleSelect}
62
+ onCancel={onCancel}
63
+ emptyLabel="no sessions found"
64
+ />
65
+ );
66
+ }
67
+
68
+ /** Case-insensitive filter over a session's subject, agent, and id. */
69
+ function filterSessions(sessions: readonly Session[], query: string): readonly Session[] {
70
+ const needle = query.trim().toLowerCase();
71
+ if (needle === "") return sessions;
72
+ return sessions.filter((s) => sessionHaystack(s).includes(needle));
73
+ }
74
+
75
+ function sessionHaystack(session: Session): string {
76
+ return [sessionSubject(session), session.spec?.agentInstanceId ?? "", session.metadata?.id ?? ""]
77
+ .join(" ")
78
+ .toLowerCase();
79
+ }
80
+
81
+ /** Map a session to a picker row: subject title, agent subtitle, age meta. */
82
+ function toSessionItem(session: Session): PickerItem {
83
+ return {
84
+ id: session.metadata?.id ?? "",
85
+ title: sessionSubject(session),
86
+ subtitle: session.spec?.agentInstanceId ?? "",
87
+ meta: relativeTime(session.status?.audit?.specAudit?.createdAt),
88
+ };
89
+ }
90
+
91
+ // The session's display subject, suppressing the backend's auto-create
92
+ // sentinel (the async title activity replaces it shortly after creation).
93
+ function sessionSubject(session: Session): string {
94
+ const subject = session.spec?.subject ?? "";
95
+ if (subject !== "" && subject !== PENDING_SUBJECT) return subject;
96
+ return session.metadata?.name !== undefined && session.metadata.name !== ""
97
+ ? session.metadata.name
98
+ : (session.metadata?.id ?? "(untitled session)");
99
+ }
100
+
101
+ // Coarse "N units ago" label from a protobuf Timestamp. Empty when unknown.
102
+ function relativeTime(ts: Timestamp | undefined): string {
103
+ if (ts === undefined) return "";
104
+ const then = Number(ts.seconds) * 1000;
105
+ if (!Number.isFinite(then) || then <= 0) return "";
106
+ const seconds = Math.max(0, Math.floor((Date.now() - then) / 1000));
107
+ const units: ReadonlyArray<readonly [number, string]> = [
108
+ [86400, "day"],
109
+ [3600, "hour"],
110
+ [60, "minute"],
111
+ ];
112
+ for (const [size, label] of units) {
113
+ const value = Math.floor(seconds / size);
114
+ if (value >= 1) return `${value} ${label}${value === 1 ? "" : "s"} ago`;
115
+ }
116
+ return "just now";
117
+ }
@@ -115,6 +115,12 @@ function describeResultView(view: ToolResultView): string | null {
115
115
  return truncate(view.text);
116
116
  case "json":
117
117
  return truncate(JSON.stringify(view.value));
118
+ case "outputRef": {
119
+ const size = view.sizeBytes ? ` (${formatBytes(view.sizeBytes)})` : "";
120
+ if (view.isImage) return `image${size}: ${view.downloadUrl}`;
121
+ const preview = view.preview ? `${truncate(view.preview)}\n` : "";
122
+ return `${preview}full output${size}: ${view.downloadUrl}`;
123
+ }
118
124
  case "error":
119
125
  return view.message;
120
126
  case "empty":
@@ -122,6 +128,12 @@ function describeResultView(view: ToolResultView): string | null {
122
128
  }
123
129
  }
124
130
 
131
+ function formatBytes(n: number): string {
132
+ if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MB`;
133
+ if (n >= 1024) return `${(n / 1024).toFixed(1)} KB`;
134
+ return `${n} B`;
135
+ }
136
+
125
137
  function truncate(s: string, maxLines = 5): string {
126
138
  const lines = s.split("\n");
127
139
  if (lines.length <= maxLines) return s;
package/src/index.ts CHANGED
@@ -24,6 +24,11 @@ export { FollowUpInput, type FollowUpInputProps } from "./components/FollowUpInp
24
24
  export { UsageWidget, type UsageWidgetProps } from "./components/UsageWidget.js";
25
25
  export { ContextGauge, type ContextGaugeProps } from "./components/ContextGauge.js";
26
26
 
27
+ // Pickers (interactive search-and-select)
28
+ export { ResourcePicker, type ResourcePickerProps, type PickerItem } from "./components/ResourcePicker.js";
29
+ export { AgentPicker, type AgentPickerProps } from "./components/AgentPicker.js";
30
+ export { SessionPicker, type SessionPickerProps } from "./components/SessionPicker.js";
31
+
27
32
  // Composed views
28
33
  export { SessionView, type SessionViewProps, type InteractionMode } from "./app/SessionView.js";
29
34
  export { SessionApp, type SessionAppProps } from "./app/SessionApp.js";