@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.
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +72 -10
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +78 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +8 -4
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +14 -10
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/composer/TiptapComposer.js +1 -1
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/extensions/SkillReference.js +1 -1
- package/dist/client/composer/extensions/SkillReference.js.map +1 -1
- package/dist/client/dynamic-suggestions.d.ts +13 -7
- package/dist/client/dynamic-suggestions.d.ts.map +1 -1
- package/dist/client/dynamic-suggestions.js +23 -12
- package/dist/client/dynamic-suggestions.js.map +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +2 -2
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/route-state.d.ts +116 -0
- package/dist/client/route-state.d.ts.map +1 -0
- package/dist/client/route-state.js +205 -0
- package/dist/client/route-state.js.map +1 -0
- package/dist/resources/store.js +4 -4
- package/dist/resources/store.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +92 -56
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agents-bundle.d.ts +6 -4
- package/dist/server/agents-bundle.d.ts.map +1 -1
- package/dist/server/agents-bundle.js +20 -12
- package/dist/server/agents-bundle.js.map +1 -1
- package/dist/templates/default/AGENTS.md +16 -8
- package/dist/templates/default/app/hooks/use-navigation-state.ts +10 -76
- package/dist/vite/agents-bundle-plugin.d.ts.map +1 -1
- package/dist/vite/agents-bundle-plugin.js +16 -6
- package/dist/vite/agents-bundle-plugin.js.map +1 -1
- package/docs/content/context-awareness.md +90 -48
- package/docs/content/creating-templates.md +22 -1
- package/docs/content/workspace.md +3 -3
- package/package.json +2 -1
- package/src/templates/default/AGENTS.md +16 -8
- 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"]}
|
package/dist/resources/store.js
CHANGED
|
@@ -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: \`
|
|
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 \`
|
|
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: \`
|
|
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: \`
|
|
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
|
`;
|