@agent-native/core 0.36.0 → 0.37.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.
Files changed (50) hide show
  1. package/dist/agent/production-agent.d.ts.map +1 -1
  2. package/dist/agent/production-agent.js +72 -10
  3. package/dist/agent/production-agent.js.map +1 -1
  4. package/dist/cli/skills.d.ts.map +1 -1
  5. package/dist/cli/skills.js +78 -0
  6. package/dist/cli/skills.js.map +1 -1
  7. package/dist/client/AssistantChat.d.ts.map +1 -1
  8. package/dist/client/AssistantChat.js +8 -4
  9. package/dist/client/AssistantChat.js.map +1 -1
  10. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  11. package/dist/client/MultiTabAssistantChat.js +14 -10
  12. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  13. package/dist/client/composer/TiptapComposer.js +1 -1
  14. package/dist/client/composer/TiptapComposer.js.map +1 -1
  15. package/dist/client/composer/extensions/SkillReference.js +1 -1
  16. package/dist/client/composer/extensions/SkillReference.js.map +1 -1
  17. package/dist/client/dynamic-suggestions.d.ts +13 -7
  18. package/dist/client/dynamic-suggestions.d.ts.map +1 -1
  19. package/dist/client/dynamic-suggestions.js +23 -12
  20. package/dist/client/dynamic-suggestions.js.map +1 -1
  21. package/dist/client/index.d.ts +1 -0
  22. package/dist/client/index.d.ts.map +1 -1
  23. package/dist/client/index.js +1 -0
  24. package/dist/client/index.js.map +1 -1
  25. package/dist/client/resources/ResourcesPanel.js +2 -2
  26. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  27. package/dist/client/route-state.d.ts +116 -0
  28. package/dist/client/route-state.d.ts.map +1 -0
  29. package/dist/client/route-state.js +205 -0
  30. package/dist/client/route-state.js.map +1 -0
  31. package/dist/resources/store.js +4 -4
  32. package/dist/resources/store.js.map +1 -1
  33. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  34. package/dist/server/agent-chat-plugin.js +92 -56
  35. package/dist/server/agent-chat-plugin.js.map +1 -1
  36. package/dist/server/agents-bundle.d.ts +6 -4
  37. package/dist/server/agents-bundle.d.ts.map +1 -1
  38. package/dist/server/agents-bundle.js +20 -12
  39. package/dist/server/agents-bundle.js.map +1 -1
  40. package/dist/templates/default/AGENTS.md +16 -8
  41. package/dist/templates/default/app/hooks/use-navigation-state.ts +10 -76
  42. package/dist/vite/agents-bundle-plugin.d.ts.map +1 -1
  43. package/dist/vite/agents-bundle-plugin.js +16 -6
  44. package/dist/vite/agents-bundle-plugin.js.map +1 -1
  45. package/docs/content/context-awareness.md +90 -48
  46. package/docs/content/creating-templates.md +22 -1
  47. package/docs/content/workspace.md +3 -3
  48. package/package.json +2 -1
  49. package/src/templates/default/AGENTS.md +16 -8
  50. package/src/templates/default/app/hooks/use-navigation-state.ts +10 -76
@@ -0,0 +1,116 @@
1
+ import { type QueryKey } from "@tanstack/react-query";
2
+ import { type Location, type NavigateOptions } from "react-router";
3
+ export interface SemanticNavigationCommandEnvelope<NavigateCommand> {
4
+ key: string;
5
+ command: NavigateCommand;
6
+ }
7
+ export interface UseSemanticNavigationStateOptions<NavigationState, NavigateCommand = NavigationState> {
8
+ /**
9
+ * Compact, semantic screen state to expose to the agent: view names, record
10
+ * IDs, active tabs, and useful aliases. Keep URL query params in the URL
11
+ * unless the app needs a human-readable semantic alias.
12
+ */
13
+ state: NavigationState | null | undefined;
14
+ /** Application-state keys the UI should write. Defaults to [`navigation`]. */
15
+ navigationKeys?: readonly string[];
16
+ /** Application-state keys to read for one-shot agent commands. Defaults to [`navigate`]. */
17
+ commandKeys?: readonly string[];
18
+ /** React Query key used for command polling/cache. Defaults to [`navigate-command`]. */
19
+ commandQueryKey?: QueryKey;
20
+ /** Request source tag for `useDbSync({ ignoreSource })` jitter prevention. */
21
+ requestSource?: string;
22
+ /** Poll interval for command reads. Defaults to 2000ms. Pass false to disable polling. */
23
+ commandRefetchInterval?: number | false;
24
+ /** Disable both navigation writes and command reads. */
25
+ enabled?: boolean;
26
+ /** Navigation writes use keepalive by default because they often fire during unload. */
27
+ keepalive?: boolean;
28
+ /** Debounce navigation writes. Defaults to 0ms. */
29
+ writeDebounceMs?: number;
30
+ /** Custom duplicate-command key. Defaults to `_writeId` or JSON content. */
31
+ getCommandDedupKey?: (command: NavigateCommand) => string;
32
+ /** Called once for each non-duplicate command after the command is consumed. */
33
+ onCommand: (command: NavigateCommand) => void | Promise<void>;
34
+ /** Optional sink for best-effort navigation write/read/delete/command errors. */
35
+ onError?: (error: unknown) => void;
36
+ }
37
+ export interface UseSemanticNavigationStateResult<NavigationState, NavigateCommand = NavigationState> {
38
+ navigationState: NavigationState | null;
39
+ command: SemanticNavigationCommandEnvelope<NavigateCommand> | null | undefined;
40
+ commandQueryKey: QueryKey;
41
+ clearCommand: () => Promise<void>;
42
+ }
43
+ export interface AgentRouteLocation {
44
+ pathname: string;
45
+ search: string;
46
+ hash: string;
47
+ searchParams: URLSearchParams;
48
+ location: Location;
49
+ }
50
+ export interface UseAgentRouteStateOptions<NavigationState, NavigateCommand = NavigationState> {
51
+ /**
52
+ * Derive compact, semantic screen state from the current React Router URL.
53
+ * The framework separately exposes raw `pathname`, `search`, and parsed
54
+ * `searchParams` through `<current-url>`.
55
+ */
56
+ getNavigationState: (location: AgentRouteLocation) => NavigationState | null | undefined;
57
+ /**
58
+ * Convert an agent-authored one-shot command into an app-local React Router
59
+ * path. Return null to consume and ignore malformed or unsupported commands.
60
+ */
61
+ getCommandPath: (command: NavigateCommand) => string | null | undefined;
62
+ /** Application-state key the UI writes. Defaults to `navigation`. */
63
+ navigationKey?: string;
64
+ /** Application-state key the agent writes for one-shot navigation. */
65
+ commandKey?: string;
66
+ /** Current browser tab id. Enables tab-scoped reads/writes. */
67
+ browserTabId?: string;
68
+ /** Request source tag for `useDbSync({ ignoreSource })` jitter prevention. */
69
+ requestSource?: string;
70
+ /**
71
+ * Also write the unscoped navigation key when browserTabId is present.
72
+ * Defaults to true so CLI/external agents still have a useful fallback.
73
+ */
74
+ writeGlobalNavigation?: boolean;
75
+ /**
76
+ * Fall back to the unscoped command key when no tab-scoped command exists.
77
+ * Defaults to true for backwards compatibility with existing navigate tools.
78
+ */
79
+ readGlobalCommandFallback?: boolean;
80
+ /** React Query key used for command polling/cache. */
81
+ commandQueryKey?: QueryKey;
82
+ /** Poll interval for command reads. Defaults to 2000ms. Pass false to disable polling. */
83
+ refetchInterval?: number | false;
84
+ /** Disable both navigation writes and command reads. */
85
+ enabled?: boolean;
86
+ /** Navigation writes use keepalive by default because they often fire during unload. */
87
+ keepalive?: boolean;
88
+ /** Debounce navigation writes. Defaults to 0ms. */
89
+ writeDebounceMs?: number;
90
+ /** Custom duplicate-command key. Defaults to `_writeId` or JSON content. */
91
+ getCommandDedupKey?: (command: NavigateCommand) => string;
92
+ /** React Router navigate options, or a function of the consumed command. */
93
+ navigateOptions?: NavigateOptions | ((command: NavigateCommand) => NavigateOptions | undefined);
94
+ /** Called after a command is consumed and before React Router navigation. */
95
+ onNavigate?: (command: NavigateCommand, path: string) => void;
96
+ /** Optional sink for best-effort navigation write/read/delete errors. */
97
+ onError?: (error: unknown) => void;
98
+ }
99
+ export interface UseAgentRouteStateResult<NavigationState, NavigateCommand = NavigationState> extends UseSemanticNavigationStateResult<NavigationState, NavigateCommand> {
100
+ }
101
+ /**
102
+ * Keeps semantic UI state agent-visible and consumes agent-authored one-shot
103
+ * commands. This is the framework primitive behind route/navigation sync; it
104
+ * intentionally knows nothing about app-specific route shapes.
105
+ */
106
+ export declare function useSemanticNavigationState<NavigationState, NavigateCommand = NavigationState>(options: UseSemanticNavigationStateOptions<NavigationState, NavigateCommand>): UseSemanticNavigationStateResult<NavigationState, NavigateCommand>;
107
+ /**
108
+ * React Router convenience wrapper around `useSemanticNavigationState`.
109
+ *
110
+ * Use URL query params as the source of truth for shareable filters. This hook
111
+ * writes semantic aliases and stable IDs to `navigation`; the framework's
112
+ * built-in URL sync separately exposes raw `pathname`, `search`, and
113
+ * `searchParams` through `<current-url>` and the `set-search-params` tool.
114
+ */
115
+ export declare function useAgentRouteState<NavigationState, NavigateCommand = NavigationState>(options: UseAgentRouteStateOptions<NavigationState, NavigateCommand>): UseAgentRouteStateResult<NavigationState, NavigateCommand>;
116
+ //# sourceMappingURL=route-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-state.d.ts","sourceRoot":"","sources":["../../src/client/route-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,205 @@
1
+ import { useCallback, useEffect, useMemo, useRef } from "react";
2
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
3
+ import { useLocation, useNavigate, } from "react-router";
4
+ import { deleteClientAppState, readClientAppState, setClientAppState, } from "./application-state.js";
5
+ const SAFE_BROWSER_TAB_ID_RE = /^[A-Za-z0-9_-]{1,96}$/;
6
+ function normalizeBrowserTabId(browserTabId) {
7
+ if (typeof browserTabId !== "string")
8
+ return undefined;
9
+ const trimmed = browserTabId.trim();
10
+ return SAFE_BROWSER_TAB_ID_RE.test(trimmed) ? trimmed : undefined;
11
+ }
12
+ function appStateKeyForBrowserTab(key, browserTabId) {
13
+ return browserTabId ? `${key}:${browserTabId}` : key;
14
+ }
15
+ function routeLocationFromReactRouter(location) {
16
+ return {
17
+ pathname: location.pathname,
18
+ search: location.search,
19
+ hash: location.hash,
20
+ searchParams: new URLSearchParams(location.search),
21
+ location,
22
+ };
23
+ }
24
+ function uniqueKeys(keys) {
25
+ return Array.from(new Set(keys));
26
+ }
27
+ function defaultCommandDedupKey(command) {
28
+ if (command && typeof command === "object" && "_writeId" in command) {
29
+ const writeId = command._writeId;
30
+ if (typeof writeId === "string" && writeId)
31
+ return writeId;
32
+ }
33
+ return JSON.stringify(command);
34
+ }
35
+ function currentRouterPath(location) {
36
+ return `${location.pathname}${location.search}${location.hash}`;
37
+ }
38
+ function stringifyForWriteDedup(value) {
39
+ try {
40
+ return JSON.stringify(value);
41
+ }
42
+ catch {
43
+ return "";
44
+ }
45
+ }
46
+ /**
47
+ * Keeps semantic UI state agent-visible and consumes agent-authored one-shot
48
+ * commands. This is the framework primitive behind route/navigation sync; it
49
+ * intentionally knows nothing about app-specific route shapes.
50
+ */
51
+ export function useSemanticNavigationState(options) {
52
+ const { requestSource, commandRefetchInterval = 2_000, enabled = true, keepalive = true, writeDebounceMs = 0, } = options;
53
+ const queryClient = useQueryClient();
54
+ const navigationKeys = useMemo(() => uniqueKeys(options.navigationKeys ?? ["navigation"]), [options.navigationKeys]);
55
+ const commandKeys = useMemo(() => uniqueKeys(options.commandKeys ?? ["navigate"]), [options.commandKeys]);
56
+ const commandQueryKey = useMemo(() => options.commandQueryKey ?? ["navigate-command"], [options.commandQueryKey]);
57
+ const navigationState = options.state ?? null;
58
+ const navigationWriteDedup = stringifyForWriteDedup({
59
+ keys: navigationKeys,
60
+ state: navigationState,
61
+ });
62
+ const getCommandDedupKeyRef = useRef(options.getCommandDedupKey);
63
+ const onCommandRef = useRef(options.onCommand);
64
+ const onErrorRef = useRef(options.onError);
65
+ getCommandDedupKeyRef.current = options.getCommandDedupKey;
66
+ onCommandRef.current = options.onCommand;
67
+ onErrorRef.current = options.onError;
68
+ const lastNavigationWriteRef = useRef(null);
69
+ useEffect(() => {
70
+ if (!enabled)
71
+ return;
72
+ if (lastNavigationWriteRef.current === navigationWriteDedup)
73
+ return;
74
+ lastNavigationWriteRef.current = navigationWriteDedup;
75
+ const write = () => {
76
+ for (const key of navigationKeys) {
77
+ setClientAppState(key, navigationState, {
78
+ keepalive,
79
+ requestSource,
80
+ }).catch((error) => onErrorRef.current?.(error));
81
+ }
82
+ };
83
+ if (writeDebounceMs > 0) {
84
+ const timer = setTimeout(write, writeDebounceMs);
85
+ return () => clearTimeout(timer);
86
+ }
87
+ write();
88
+ }, [
89
+ enabled,
90
+ keepalive,
91
+ navigationKeys,
92
+ navigationState,
93
+ navigationWriteDedup,
94
+ requestSource,
95
+ writeDebounceMs,
96
+ ]);
97
+ const commandQuery = useQuery({
98
+ queryKey: commandQueryKey,
99
+ enabled,
100
+ retry: false,
101
+ refetchInterval: commandRefetchInterval,
102
+ queryFn: async () => {
103
+ for (const key of commandKeys) {
104
+ const command = await readClientAppState(key);
105
+ if (command !== null && command !== undefined) {
106
+ return { key, command };
107
+ }
108
+ }
109
+ return null;
110
+ },
111
+ });
112
+ const clearCommand = useCallback(async () => {
113
+ await Promise.all(commandKeys.map((key) => deleteClientAppState(key, { requestSource }).catch((error) => {
114
+ onErrorRef.current?.(error);
115
+ })));
116
+ queryClient.setQueryData(commandQueryKey, null);
117
+ }, [commandKeys, commandQueryKey, queryClient, requestSource]);
118
+ const lastProcessedDedupKeyRef = useRef(null);
119
+ useEffect(() => {
120
+ const envelope = commandQuery.data;
121
+ if (!enabled || !envelope)
122
+ return;
123
+ const dedupKey = getCommandDedupKeyRef.current?.(envelope.command) ??
124
+ defaultCommandDedupKey(envelope.command);
125
+ const consume = () => {
126
+ deleteClientAppState(envelope.key, { requestSource }).catch((error) => onErrorRef.current?.(error));
127
+ queryClient.setQueryData(commandQueryKey, null);
128
+ };
129
+ if (lastProcessedDedupKeyRef.current === dedupKey) {
130
+ consume();
131
+ return;
132
+ }
133
+ lastProcessedDedupKeyRef.current = dedupKey;
134
+ consume();
135
+ Promise.resolve(onCommandRef.current(envelope.command)).catch((error) => onErrorRef.current?.(error));
136
+ }, [commandQuery.data, commandQueryKey, enabled, queryClient, requestSource]);
137
+ return {
138
+ navigationState,
139
+ command: commandQuery.data,
140
+ commandQueryKey,
141
+ clearCommand,
142
+ };
143
+ }
144
+ /**
145
+ * React Router convenience wrapper around `useSemanticNavigationState`.
146
+ *
147
+ * Use URL query params as the source of truth for shareable filters. This hook
148
+ * writes semantic aliases and stable IDs to `navigation`; the framework's
149
+ * built-in URL sync separately exposes raw `pathname`, `search`, and
150
+ * `searchParams` through `<current-url>` and the `set-search-params` tool.
151
+ */
152
+ export function useAgentRouteState(options) {
153
+ const { navigationKey = "navigation", commandKey = "navigate", writeGlobalNavigation = true, readGlobalCommandFallback = true, } = options;
154
+ const location = useLocation();
155
+ const navigate = useNavigate();
156
+ const browserTabId = useMemo(() => normalizeBrowserTabId(options.browserTabId), [options.browserTabId]);
157
+ const navigationKeys = useMemo(() => {
158
+ const scopedKey = appStateKeyForBrowserTab(navigationKey, browserTabId);
159
+ const keys = [scopedKey];
160
+ if (browserTabId && writeGlobalNavigation)
161
+ keys.push(navigationKey);
162
+ return uniqueKeys(keys);
163
+ }, [browserTabId, navigationKey, writeGlobalNavigation]);
164
+ const commandKeys = useMemo(() => {
165
+ const scopedKey = appStateKeyForBrowserTab(commandKey, browserTabId);
166
+ const keys = [scopedKey];
167
+ if (browserTabId && readGlobalCommandFallback)
168
+ keys.push(commandKey);
169
+ return uniqueKeys(keys);
170
+ }, [browserTabId, commandKey, readGlobalCommandFallback]);
171
+ const commandQueryKey = useMemo(() => options.commandQueryKey ?? [
172
+ "navigate-command",
173
+ commandKey,
174
+ browserTabId ?? "global",
175
+ ], [browserTabId, commandKey, options.commandQueryKey]);
176
+ const routeLocation = useMemo(() => routeLocationFromReactRouter(location), [location]);
177
+ const navigationState = options.getNavigationState(routeLocation) ?? null;
178
+ return useSemanticNavigationState({
179
+ state: navigationState,
180
+ navigationKeys,
181
+ commandKeys,
182
+ commandQueryKey,
183
+ requestSource: options.requestSource,
184
+ commandRefetchInterval: options.refetchInterval,
185
+ enabled: options.enabled,
186
+ keepalive: options.keepalive,
187
+ writeDebounceMs: options.writeDebounceMs,
188
+ getCommandDedupKey: options.getCommandDedupKey,
189
+ onError: options.onError,
190
+ onCommand: (command) => {
191
+ const path = options.getCommandPath(command);
192
+ if (!path)
193
+ return;
194
+ options.onNavigate?.(command, path);
195
+ if (path === currentRouterPath(location))
196
+ return;
197
+ const navigateOptions = options.navigateOptions;
198
+ const resolvedOptions = typeof navigateOptions === "function"
199
+ ? navigateOptions(command)
200
+ : navigateOptions;
201
+ navigate(path, resolvedOptions);
202
+ },
203
+ });
204
+ }
205
+ //# sourceMappingURL=route-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-state.js","sourceRoot":"","sources":["../../src/client/route-state.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { useQuery, useQueryClient, type QueryKey } from \"@tanstack/react-query\";\nimport {\n useLocation,\n useNavigate,\n type Location,\n type NavigateOptions,\n} from \"react-router\";\nimport {\n deleteClientAppState,\n readClientAppState,\n setClientAppState,\n} from \"./application-state.js\";\n\nconst SAFE_BROWSER_TAB_ID_RE = /^[A-Za-z0-9_-]{1,96}$/;\n\nexport interface SemanticNavigationCommandEnvelope<NavigateCommand> {\n key: string;\n command: NavigateCommand;\n}\n\nexport interface UseSemanticNavigationStateOptions<\n NavigationState,\n NavigateCommand = NavigationState,\n> {\n /**\n * Compact, semantic screen state to expose to the agent: view names, record\n * IDs, active tabs, and useful aliases. Keep URL query params in the URL\n * unless the app needs a human-readable semantic alias.\n */\n state: NavigationState | null | undefined;\n /** Application-state keys the UI should write. Defaults to [`navigation`]. */\n navigationKeys?: readonly string[];\n /** Application-state keys to read for one-shot agent commands. Defaults to [`navigate`]. */\n commandKeys?: readonly string[];\n /** React Query key used for command polling/cache. Defaults to [`navigate-command`]. */\n commandQueryKey?: QueryKey;\n /** Request source tag for `useDbSync({ ignoreSource })` jitter prevention. */\n requestSource?: string;\n /** Poll interval for command reads. Defaults to 2000ms. Pass false to disable polling. */\n commandRefetchInterval?: number | false;\n /** Disable both navigation writes and command reads. */\n enabled?: boolean;\n /** Navigation writes use keepalive by default because they often fire during unload. */\n keepalive?: boolean;\n /** Debounce navigation writes. Defaults to 0ms. */\n writeDebounceMs?: number;\n /** Custom duplicate-command key. Defaults to `_writeId` or JSON content. */\n getCommandDedupKey?: (command: NavigateCommand) => string;\n /** Called once for each non-duplicate command after the command is consumed. */\n onCommand: (command: NavigateCommand) => void | Promise<void>;\n /** Optional sink for best-effort navigation write/read/delete/command errors. */\n onError?: (error: unknown) => void;\n}\n\nexport interface UseSemanticNavigationStateResult<\n NavigationState,\n NavigateCommand = NavigationState,\n> {\n navigationState: NavigationState | null;\n command:\n | SemanticNavigationCommandEnvelope<NavigateCommand>\n | null\n | undefined;\n commandQueryKey: QueryKey;\n clearCommand: () => Promise<void>;\n}\n\nexport interface AgentRouteLocation {\n pathname: string;\n search: string;\n hash: string;\n searchParams: URLSearchParams;\n location: Location;\n}\n\nexport interface UseAgentRouteStateOptions<\n NavigationState,\n NavigateCommand = NavigationState,\n> {\n /**\n * Derive compact, semantic screen state from the current React Router URL.\n * The framework separately exposes raw `pathname`, `search`, and parsed\n * `searchParams` through `<current-url>`.\n */\n getNavigationState: (\n location: AgentRouteLocation,\n ) => NavigationState | null | undefined;\n /**\n * Convert an agent-authored one-shot command into an app-local React Router\n * path. Return null to consume and ignore malformed or unsupported commands.\n */\n getCommandPath: (command: NavigateCommand) => string | null | undefined;\n /** Application-state key the UI writes. Defaults to `navigation`. */\n navigationKey?: string;\n /** Application-state key the agent writes for one-shot navigation. */\n commandKey?: string;\n /** Current browser tab id. Enables tab-scoped reads/writes. */\n browserTabId?: string;\n /** Request source tag for `useDbSync({ ignoreSource })` jitter prevention. */\n requestSource?: string;\n /**\n * Also write the unscoped navigation key when browserTabId is present.\n * Defaults to true so CLI/external agents still have a useful fallback.\n */\n writeGlobalNavigation?: boolean;\n /**\n * Fall back to the unscoped command key when no tab-scoped command exists.\n * Defaults to true for backwards compatibility with existing navigate tools.\n */\n readGlobalCommandFallback?: boolean;\n /** React Query key used for command polling/cache. */\n commandQueryKey?: QueryKey;\n /** Poll interval for command reads. Defaults to 2000ms. Pass false to disable polling. */\n refetchInterval?: number | false;\n /** Disable both navigation writes and command reads. */\n enabled?: boolean;\n /** Navigation writes use keepalive by default because they often fire during unload. */\n keepalive?: boolean;\n /** Debounce navigation writes. Defaults to 0ms. */\n writeDebounceMs?: number;\n /** Custom duplicate-command key. Defaults to `_writeId` or JSON content. */\n getCommandDedupKey?: (command: NavigateCommand) => string;\n /** React Router navigate options, or a function of the consumed command. */\n navigateOptions?:\n | NavigateOptions\n | ((command: NavigateCommand) => NavigateOptions | undefined);\n /** Called after a command is consumed and before React Router navigation. */\n onNavigate?: (command: NavigateCommand, path: string) => void;\n /** Optional sink for best-effort navigation write/read/delete errors. */\n onError?: (error: unknown) => void;\n}\n\nexport interface UseAgentRouteStateResult<\n NavigationState,\n NavigateCommand = NavigationState,\n> extends UseSemanticNavigationStateResult<NavigationState, NavigateCommand> {}\n\nfunction normalizeBrowserTabId(browserTabId?: string): string | undefined {\n if (typeof browserTabId !== \"string\") return undefined;\n const trimmed = browserTabId.trim();\n return SAFE_BROWSER_TAB_ID_RE.test(trimmed) ? trimmed : undefined;\n}\n\nfunction appStateKeyForBrowserTab(key: string, browserTabId?: string): string {\n return browserTabId ? `${key}:${browserTabId}` : key;\n}\n\nfunction routeLocationFromReactRouter(location: Location): AgentRouteLocation {\n return {\n pathname: location.pathname,\n search: location.search,\n hash: location.hash,\n searchParams: new URLSearchParams(location.search),\n location,\n };\n}\n\nfunction uniqueKeys(keys: readonly string[]): string[] {\n return Array.from(new Set(keys));\n}\n\nfunction defaultCommandDedupKey(command: unknown): string {\n if (command && typeof command === \"object\" && \"_writeId\" in command) {\n const writeId = (command as { _writeId?: unknown })._writeId;\n if (typeof writeId === \"string\" && writeId) return writeId;\n }\n return JSON.stringify(command);\n}\n\nfunction currentRouterPath(location: Location): string {\n return `${location.pathname}${location.search}${location.hash}`;\n}\n\nfunction stringifyForWriteDedup(value: unknown): string {\n try {\n return JSON.stringify(value);\n } catch {\n return \"\";\n }\n}\n\n/**\n * Keeps semantic UI state agent-visible and consumes agent-authored one-shot\n * commands. This is the framework primitive behind route/navigation sync; it\n * intentionally knows nothing about app-specific route shapes.\n */\nexport function useSemanticNavigationState<\n NavigationState,\n NavigateCommand = NavigationState,\n>(\n options: UseSemanticNavigationStateOptions<NavigationState, NavigateCommand>,\n): UseSemanticNavigationStateResult<NavigationState, NavigateCommand> {\n const {\n requestSource,\n commandRefetchInterval = 2_000,\n enabled = true,\n keepalive = true,\n writeDebounceMs = 0,\n } = options;\n\n const queryClient = useQueryClient();\n const navigationKeys = useMemo(\n () => uniqueKeys(options.navigationKeys ?? [\"navigation\"]),\n [options.navigationKeys],\n );\n const commandKeys = useMemo(\n () => uniqueKeys(options.commandKeys ?? [\"navigate\"]),\n [options.commandKeys],\n );\n const commandQueryKey = useMemo<QueryKey>(\n () => options.commandQueryKey ?? [\"navigate-command\"],\n [options.commandQueryKey],\n );\n const navigationState = options.state ?? null;\n const navigationWriteDedup = stringifyForWriteDedup({\n keys: navigationKeys,\n state: navigationState,\n });\n\n const getCommandDedupKeyRef = useRef(options.getCommandDedupKey);\n const onCommandRef = useRef(options.onCommand);\n const onErrorRef = useRef(options.onError);\n getCommandDedupKeyRef.current = options.getCommandDedupKey;\n onCommandRef.current = options.onCommand;\n onErrorRef.current = options.onError;\n\n const lastNavigationWriteRef = useRef<string | null>(null);\n\n useEffect(() => {\n if (!enabled) return;\n if (lastNavigationWriteRef.current === navigationWriteDedup) return;\n lastNavigationWriteRef.current = navigationWriteDedup;\n\n const write = () => {\n for (const key of navigationKeys) {\n setClientAppState(key, navigationState, {\n keepalive,\n requestSource,\n }).catch((error) => onErrorRef.current?.(error));\n }\n };\n\n if (writeDebounceMs > 0) {\n const timer = setTimeout(write, writeDebounceMs);\n return () => clearTimeout(timer);\n }\n write();\n }, [\n enabled,\n keepalive,\n navigationKeys,\n navigationState,\n navigationWriteDedup,\n requestSource,\n writeDebounceMs,\n ]);\n\n const commandQuery =\n useQuery<SemanticNavigationCommandEnvelope<NavigateCommand> | null>({\n queryKey: commandQueryKey,\n enabled,\n retry: false,\n refetchInterval: commandRefetchInterval,\n queryFn: async () => {\n for (const key of commandKeys) {\n const command = await readClientAppState<NavigateCommand>(key);\n if (command !== null && command !== undefined) {\n return { key, command };\n }\n }\n return null;\n },\n });\n\n const clearCommand = useCallback(async () => {\n await Promise.all(\n commandKeys.map((key) =>\n deleteClientAppState(key, { requestSource }).catch((error) => {\n onErrorRef.current?.(error);\n }),\n ),\n );\n queryClient.setQueryData(commandQueryKey, null);\n }, [commandKeys, commandQueryKey, queryClient, requestSource]);\n\n const lastProcessedDedupKeyRef = useRef<string | null>(null);\n\n useEffect(() => {\n const envelope = commandQuery.data;\n if (!enabled || !envelope) return;\n\n const dedupKey =\n getCommandDedupKeyRef.current?.(envelope.command) ??\n defaultCommandDedupKey(envelope.command);\n const consume = () => {\n deleteClientAppState(envelope.key, { requestSource }).catch((error) =>\n onErrorRef.current?.(error),\n );\n queryClient.setQueryData(commandQueryKey, null);\n };\n\n if (lastProcessedDedupKeyRef.current === dedupKey) {\n consume();\n return;\n }\n lastProcessedDedupKeyRef.current = dedupKey;\n consume();\n\n Promise.resolve(onCommandRef.current(envelope.command)).catch((error) =>\n onErrorRef.current?.(error),\n );\n }, [commandQuery.data, commandQueryKey, enabled, queryClient, requestSource]);\n\n return {\n navigationState,\n command: commandQuery.data,\n commandQueryKey,\n clearCommand,\n };\n}\n\n/**\n * React Router convenience wrapper around `useSemanticNavigationState`.\n *\n * Use URL query params as the source of truth for shareable filters. This hook\n * writes semantic aliases and stable IDs to `navigation`; the framework's\n * built-in URL sync separately exposes raw `pathname`, `search`, and\n * `searchParams` through `<current-url>` and the `set-search-params` tool.\n */\nexport function useAgentRouteState<\n NavigationState,\n NavigateCommand = NavigationState,\n>(\n options: UseAgentRouteStateOptions<NavigationState, NavigateCommand>,\n): UseAgentRouteStateResult<NavigationState, NavigateCommand> {\n const {\n navigationKey = \"navigation\",\n commandKey = \"navigate\",\n writeGlobalNavigation = true,\n readGlobalCommandFallback = true,\n } = options;\n\n const location = useLocation();\n const navigate = useNavigate();\n const browserTabId = useMemo(\n () => normalizeBrowserTabId(options.browserTabId),\n [options.browserTabId],\n );\n const navigationKeys = useMemo(() => {\n const scopedKey = appStateKeyForBrowserTab(navigationKey, browserTabId);\n const keys = [scopedKey];\n if (browserTabId && writeGlobalNavigation) keys.push(navigationKey);\n return uniqueKeys(keys);\n }, [browserTabId, navigationKey, writeGlobalNavigation]);\n const commandKeys = useMemo(() => {\n const scopedKey = appStateKeyForBrowserTab(commandKey, browserTabId);\n const keys = [scopedKey];\n if (browserTabId && readGlobalCommandFallback) keys.push(commandKey);\n return uniqueKeys(keys);\n }, [browserTabId, commandKey, readGlobalCommandFallback]);\n const commandQueryKey = useMemo<QueryKey>(\n () =>\n options.commandQueryKey ?? [\n \"navigate-command\",\n commandKey,\n browserTabId ?? \"global\",\n ],\n [browserTabId, commandKey, options.commandQueryKey],\n );\n\n const routeLocation = useMemo(\n () => routeLocationFromReactRouter(location),\n [location],\n );\n const navigationState = options.getNavigationState(routeLocation) ?? null;\n\n return useSemanticNavigationState<NavigationState, NavigateCommand>({\n state: navigationState,\n navigationKeys,\n commandKeys,\n commandQueryKey,\n requestSource: options.requestSource,\n commandRefetchInterval: options.refetchInterval,\n enabled: options.enabled,\n keepalive: options.keepalive,\n writeDebounceMs: options.writeDebounceMs,\n getCommandDedupKey: options.getCommandDedupKey,\n onError: options.onError,\n onCommand: (command) => {\n const path = options.getCommandPath(command);\n if (!path) return;\n options.onNavigate?.(command, path);\n if (path === currentRouterPath(location)) return;\n\n const navigateOptions = options.navigateOptions;\n const resolvedOptions =\n typeof navigateOptions === \"function\"\n ? navigateOptions(command)\n : navigateOptions;\n navigate(path, resolvedOptions);\n },\n });\n}\n"]}
@@ -60,9 +60,9 @@ Review the current conversation and save anything worth remembering using the st
60
60
  ## Steps
61
61
 
62
62
  1. Review the conversation for new insights
63
- 2. Check your memory index: \`resource-read --path memory/MEMORY.md\`
63
+ 2. Check your memory index with the \`resources\` tool: \`action: "read"\`, \`path: "memory/MEMORY.md"\`
64
64
  3. For each new insight, use \`save-memory\` with a descriptive name, type, and content
65
- 4. If updating an existing memory, read it first with \`resource-read --path memory/<name>.md\`, then save with merged content
65
+ 4. If updating an existing memory, read it first with the \`resources\` tool (\`action: "read"\`, \`path: "memory/<name>.md"\`), then save with merged content
66
66
 
67
67
  ## What NOT to capture
68
68
 
@@ -100,10 +100,10 @@ Review the current conversation and update the shared \`LEARNINGS.md\` resource
100
100
 
101
101
  ## Steps
102
102
 
103
- 1. Read shared learnings: \`pnpm action resource-read --path LEARNINGS.md --scope shared\`
103
+ 1. Read shared learnings with the \`resources\` tool: \`action: "read"\`, \`path: "LEARNINGS.md"\`, \`scope: "shared"\`
104
104
  2. Review the conversation for team-relevant insights
105
105
  3. Merge new learnings with existing ones — don't duplicate, refine existing entries
106
- 4. Write back: \`pnpm action resource-write --path LEARNINGS.md --scope shared --content "..."\`
106
+ 4. Write back with the \`resources\` tool: \`action: "write"\`, \`path: "LEARNINGS.md"\`, \`scope: "shared"\`, \`content: "..."\`
107
107
 
108
108
  Keep entries concise — one line per learning, grouped by category (Conventions, Technical, Patterns).
109
109
  `;