@agent-native/core 0.106.0 → 0.106.2
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/artifact-response.ts +84 -0
- package/corpus/core/src/agent/thread-data-builder.ts +1 -1
- package/corpus/core/src/application-state/index.ts +2 -0
- package/corpus/core/src/application-state/script-helpers.ts +12 -0
- package/corpus/core/src/application-state/store.ts +38 -0
- package/corpus/core/src/client/review/ReviewCommentComposer.tsx +10 -3
- package/corpus/core/src/integrations/webhook-handler.ts +41 -5
- package/corpus/core/src/server/agent-chat-plugin.ts +7 -2
- package/corpus/templates/clips/app/components/capture-install-options.tsx +60 -5
- package/corpus/templates/clips/app/components/library/library-layout.tsx +9 -1
- package/corpus/templates/clips/app/hooks/use-desktop-promo.ts +4 -4
- package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
- package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/clips/app/lib/capture-install-options.ts +89 -4
- package/corpus/templates/clips/app/routes/_app.dictate.tsx +10 -1
- package/corpus/templates/clips/app/routes/record.tsx +1 -0
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +7 -0
- package/corpus/templates/clips/changelog/2026-07-16-the-desktop-app-button-now-shows-open-desktop-app-and-launch.md +6 -0
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +5 -0
- package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +1 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +41 -6
- package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +5 -0
- package/corpus/templates/content/.agents/skills/content/SKILL.md +31 -0
- package/corpus/templates/content/changelog/2026-07-16-slack-corrections-now-preserve-newer-content-values-unless-y.md +6 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +22 -0
- package/corpus/templates/design/AGENTS.md +16 -0
- package/corpus/templates/design/actions/cancel-node-rewrite-request.ts +54 -0
- package/corpus/templates/design/actions/propose-node-rewrite.ts +290 -0
- package/corpus/templates/design/actions/resolve-node-rewrite.ts +257 -0
- package/corpus/templates/design/actions/view-screen.ts +98 -2
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +71 -1
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +19 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +33 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +194 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +21 -4
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +18 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -0
- package/corpus/templates/design/app/components/visual-editor/NodeRewriteProposal.tsx +638 -0
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +535 -54
- package/corpus/templates/design/app/components/visual-editor/index.ts +5 -1
- package/corpus/templates/design/app/components/visual-editor/review-canvas-state.ts +21 -3
- package/corpus/templates/design/app/i18n/ar-SA.ts +35 -0
- package/corpus/templates/design/app/i18n/de-DE.ts +35 -0
- package/corpus/templates/design/app/i18n/es-ES.ts +35 -0
- package/corpus/templates/design/app/i18n/fr-FR.ts +35 -0
- package/corpus/templates/design/app/i18n/hi-IN.ts +35 -0
- package/corpus/templates/design/app/i18n/ja-JP.ts +35 -0
- package/corpus/templates/design/app/i18n/ko-KR.ts +35 -0
- package/corpus/templates/design/app/i18n/pt-BR.ts +35 -0
- package/corpus/templates/design/app/i18n/zh-CN.ts +35 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +32 -0
- package/corpus/templates/design/app/i18n-data.ts +32 -0
- package/corpus/templates/design/app/lib/node-reprompt.ts +110 -0
- package/corpus/templates/design/changelog/2026-07-16-design-editing-and-review-are-now-more-predictable.md +6 -0
- package/corpus/templates/design/server/lib/reprompt-action-guard.ts +107 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +9 -1
- package/corpus/templates/design/shared/code-layer.ts +15 -0
- package/corpus/templates/design/shared/node-rewrite.ts +213 -0
- package/corpus/templates/design/shared/review-anchor.ts +26 -3
- package/dist/a2a/artifact-response.d.ts +9 -0
- package/dist/a2a/artifact-response.d.ts.map +1 -1
- package/dist/a2a/artifact-response.js +68 -0
- package/dist/a2a/artifact-response.js.map +1 -1
- package/dist/agent/thread-data-builder.js +1 -1
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/application-state/index.d.ts +2 -2
- package/dist/application-state/index.d.ts.map +1 -1
- package/dist/application-state/index.js +2 -2
- package/dist/application-state/index.js.map +1 -1
- package/dist/application-state/script-helpers.d.ts +1 -0
- package/dist/application-state/script-helpers.d.ts.map +1 -1
- package/dist/application-state/script-helpers.js +7 -1
- package/dist/application-state/script-helpers.js.map +1 -1
- package/dist/application-state/store.d.ts +1 -0
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +28 -0
- package/dist/application-state/store.js.map +1 -1
- package/dist/client/review/ReviewCommentComposer.d.ts +4 -1
- package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
- package/dist/client/review/ReviewCommentComposer.js +3 -3
- package/dist/client/review/ReviewCommentComposer.js.map +1 -1
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +24 -5
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +3 -1
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1093,6 +1093,7 @@ Clips में उपयोगकर्ताओं को दिखने व
|
|
|
1093
1093
|
desktopTitle: "Desktop app (स्थानीयकृत)",
|
|
1094
1094
|
desktopDescription:
|
|
1095
1095
|
"Most seamless for global shortcuts, menu-bar recording, meetings, and repeat captures. (स्थानीयकृत)",
|
|
1096
|
+
openDesktopApp: "Open desktop app (स्थानीयकृत)",
|
|
1096
1097
|
},
|
|
1097
1098
|
editableTitle: {
|
|
1098
1099
|
untitled: "Untitled Clip (स्थानीयकृत)",
|
|
@@ -1114,6 +1114,7 @@ Clips のユーザー向けの主な変更はここに記録されます。コ
|
|
|
1114
1114
|
desktopTitle: "Desktop app (ローカライズ済み)",
|
|
1115
1115
|
desktopDescription:
|
|
1116
1116
|
"Most seamless for global shortcuts, menu-bar recording, meetings, and repeat captures. (ローカライズ済み)",
|
|
1117
|
+
openDesktopApp: "Open desktop app (ローカライズ済み)",
|
|
1117
1118
|
},
|
|
1118
1119
|
editableTitle: {
|
|
1119
1120
|
untitled: "Untitled Clip (ローカライズ済み)",
|
|
@@ -1103,6 +1103,7 @@ Clips의 모든 사용자 대상 변경 사항은 여기에 기록됩니다. 명
|
|
|
1103
1103
|
desktopTitle: "Desktop app (현지화됨)",
|
|
1104
1104
|
desktopDescription:
|
|
1105
1105
|
"Most seamless for global shortcuts, menu-bar recording, meetings, and repeat captures. (현지화됨)",
|
|
1106
|
+
openDesktopApp: "Open desktop app (현지화됨)",
|
|
1106
1107
|
},
|
|
1107
1108
|
editableTitle: {
|
|
1108
1109
|
untitled: "Untitled Clip (현지화됨)",
|
|
@@ -1121,6 +1121,7 @@ Todas as mudanças visíveis para usuários do Clips são documentadas aqui. Voc
|
|
|
1121
1121
|
desktopTitle: "Desktop app (Localizado)",
|
|
1122
1122
|
desktopDescription:
|
|
1123
1123
|
"Most seamless for global shortcuts, menu-bar recording, meetings, and repeat captures. (Localizado)",
|
|
1124
|
+
openDesktopApp: "Open desktop app (Localizado)",
|
|
1124
1125
|
},
|
|
1125
1126
|
editableTitle: {
|
|
1126
1127
|
untitled: "Untitled Clip (Localizado)",
|
|
@@ -1064,6 +1064,7 @@ Clips 中所有面向用户的重要更改都会记录在这里。你可以随
|
|
|
1064
1064
|
desktopTitle: "Desktop app (已本地化)",
|
|
1065
1065
|
desktopDescription:
|
|
1066
1066
|
"Most seamless for global shortcuts, menu-bar recording, meetings, and repeat captures. (已本地化)",
|
|
1067
|
+
openDesktopApp: "Open desktop app (已本地化)",
|
|
1067
1068
|
},
|
|
1068
1069
|
editableTitle: {
|
|
1069
1070
|
untitled: "Untitled Clip (已本地化)",
|
|
@@ -1,22 +1,107 @@
|
|
|
1
|
-
|
|
1
|
+
import { appPath } from "@agent-native/core/client";
|
|
2
|
+
|
|
3
|
+
const DESKTOP_PROMO_DISMISSED_STORAGE_KEY = "clips.desktop-promo.dismissed";
|
|
4
|
+
const DESKTOP_DOWNLOADED_STORAGE_KEY = "clips.desktop-app.downloaded";
|
|
5
|
+
|
|
6
|
+
// Custom scheme the desktop build registers. A web click tries this first and
|
|
7
|
+
// falls back to the download page when nothing handles it.
|
|
8
|
+
const DESKTOP_APP_PROTOCOL_URL = "clips://open";
|
|
9
|
+
const DESKTOP_APP_LAUNCH_FALLBACK_MS = 800;
|
|
2
10
|
|
|
3
11
|
export function hasDownloadedDesktopApp(): boolean {
|
|
4
12
|
try {
|
|
13
|
+
if (typeof window === "undefined") return false;
|
|
14
|
+
const ls = window.localStorage;
|
|
15
|
+
// Also treat the legacy dismissed flag as "downloaded" — before the flag
|
|
16
|
+
// split, a single key covered both states, so existing users only have it.
|
|
5
17
|
return (
|
|
6
|
-
|
|
7
|
-
|
|
18
|
+
ls?.getItem(DESKTOP_DOWNLOADED_STORAGE_KEY) === "1" ||
|
|
19
|
+
ls?.getItem(DESKTOP_PROMO_DISMISSED_STORAGE_KEY) === "1"
|
|
8
20
|
);
|
|
9
21
|
} catch {
|
|
10
22
|
return false;
|
|
11
23
|
}
|
|
12
24
|
}
|
|
13
25
|
|
|
26
|
+
const downloadedListeners = new Set<() => void>();
|
|
27
|
+
|
|
28
|
+
export function subscribeDownloaded(callback: () => void): () => void {
|
|
29
|
+
downloadedListeners.add(callback);
|
|
30
|
+
return () => {
|
|
31
|
+
downloadedListeners.delete(callback);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
14
35
|
export function markDesktopAppDownloaded(): void {
|
|
15
36
|
try {
|
|
16
|
-
|
|
37
|
+
// Downloading (or successfully launching) the app also hides the promo.
|
|
38
|
+
window.localStorage?.setItem(DESKTOP_DOWNLOADED_STORAGE_KEY, "1");
|
|
39
|
+
window.localStorage?.setItem(DESKTOP_PROMO_DISMISSED_STORAGE_KEY, "1");
|
|
17
40
|
} catch {
|
|
18
41
|
// Download tracking is best-effort and must not block the installer.
|
|
19
42
|
}
|
|
43
|
+
downloadedListeners.forEach((fn) => fn());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function hasDismissedDesktopPromo(): boolean {
|
|
47
|
+
try {
|
|
48
|
+
return (
|
|
49
|
+
typeof window !== "undefined" &&
|
|
50
|
+
window.localStorage?.getItem(DESKTOP_PROMO_DISMISSED_STORAGE_KEY) === "1"
|
|
51
|
+
);
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function markDesktopPromoDismissed(): void {
|
|
58
|
+
try {
|
|
59
|
+
window.localStorage?.setItem(DESKTOP_PROMO_DISMISSED_STORAGE_KEY, "1");
|
|
60
|
+
} catch {
|
|
61
|
+
// Dismiss tracking is best-effort.
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Try to launch the installed desktop app via its custom protocol, falling back
|
|
67
|
+
* to the download page when nothing handles the scheme. Browsers expose no way
|
|
68
|
+
* to query whether the protocol is registered, so we watch for the tab losing
|
|
69
|
+
* focus (the app taking over) within a short window; if that never happens we
|
|
70
|
+
* assume the app is not installed and navigate to the fallback. A successful
|
|
71
|
+
* launch self-heals the stored "downloaded" flag.
|
|
72
|
+
*/
|
|
73
|
+
export function attemptOpenDesktopApp(fallbackHref = "/download"): void {
|
|
74
|
+
if (typeof window === "undefined") return;
|
|
75
|
+
const fallbackUrl = appPath(fallbackHref);
|
|
76
|
+
|
|
77
|
+
let launched = false;
|
|
78
|
+
const cleanup = () => {
|
|
79
|
+
window.removeEventListener("blur", onLeave);
|
|
80
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
81
|
+
};
|
|
82
|
+
const onLeave = () => {
|
|
83
|
+
if (launched) return;
|
|
84
|
+
launched = true;
|
|
85
|
+
markDesktopAppDownloaded();
|
|
86
|
+
cleanup();
|
|
87
|
+
};
|
|
88
|
+
const onVisibility = () => {
|
|
89
|
+
if (document.hidden) onLeave();
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
window.addEventListener("blur", onLeave);
|
|
93
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
94
|
+
|
|
95
|
+
window.setTimeout(() => {
|
|
96
|
+
cleanup();
|
|
97
|
+
if (!launched) window.location.href = fallbackUrl;
|
|
98
|
+
}, DESKTOP_APP_LAUNCH_FALLBACK_MS);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
window.location.href = DESKTOP_APP_PROTOCOL_URL;
|
|
102
|
+
} catch {
|
|
103
|
+
// Some browsers throw on an unknown scheme; the timeout handles fallback.
|
|
104
|
+
}
|
|
20
105
|
}
|
|
21
106
|
|
|
22
107
|
function isFalsy(value: unknown): boolean {
|
|
@@ -640,7 +640,16 @@ function EmptyState({ isDesktopApp }: { isDesktopApp: boolean }) {
|
|
|
640
640
|
{t("dictateRoute.emptyWebDescription")}
|
|
641
641
|
</p>
|
|
642
642
|
<div className="mt-5 flex items-center justify-center">
|
|
643
|
-
<CaptureInstallButton
|
|
643
|
+
<CaptureInstallButton
|
|
644
|
+
size="sm"
|
|
645
|
+
className="gap-1.5"
|
|
646
|
+
downloadedChildren={
|
|
647
|
+
<>
|
|
648
|
+
<IconDeviceDesktop className="h-3.5 w-3.5" />
|
|
649
|
+
{t("captureInstall.openDesktopApp")}
|
|
650
|
+
</>
|
|
651
|
+
}
|
|
652
|
+
>
|
|
644
653
|
<IconDownload className="h-3.5 w-3.5" />
|
|
645
654
|
{t("dictateRoute.downloadDesktopApp")}
|
|
646
655
|
</CaptureInstallButton>
|
|
@@ -670,6 +670,7 @@ function DesktopRecorderCallout() {
|
|
|
670
670
|
<CaptureInstallButton
|
|
671
671
|
size="sm"
|
|
672
672
|
className="mt-4 w-full bg-primary text-primary-foreground hover:bg-primary/90"
|
|
673
|
+
downloadedChildren={t("captureInstall.openDesktopApp")}
|
|
673
674
|
>
|
|
674
675
|
{t("recordRoute.downloadDesktopApp")}
|
|
675
676
|
</CaptureInstallButton>
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
IconAlertTriangle,
|
|
13
13
|
IconArrowLeft,
|
|
14
|
+
IconDeviceDesktop,
|
|
14
15
|
IconDownload,
|
|
15
16
|
IconDots,
|
|
16
17
|
IconExternalLink,
|
|
@@ -1220,6 +1221,12 @@ function PublicAgentEmptyState({
|
|
|
1220
1221
|
className="w-full gap-2"
|
|
1221
1222
|
align="center"
|
|
1222
1223
|
onClick={() => onCtaClick("download")}
|
|
1224
|
+
downloadedChildren={
|
|
1225
|
+
<>
|
|
1226
|
+
<IconDeviceDesktop className="h-4 w-4" />
|
|
1227
|
+
{t("captureInstall.openDesktopApp")}
|
|
1228
|
+
</>
|
|
1229
|
+
}
|
|
1223
1230
|
>
|
|
1224
1231
|
<IconDownload className="h-4 w-4" />
|
|
1225
1232
|
{downloadLabel}
|
|
@@ -34,6 +34,11 @@ tauri = { version = "2", features = [
|
|
|
34
34
|
tauri-plugin-shell = "2"
|
|
35
35
|
tauri-plugin-global-shortcut = "2"
|
|
36
36
|
tauri-plugin-single-instance = "2"
|
|
37
|
+
# Registers the `clips://` custom URL scheme so a web "Open desktop app"
|
|
38
|
+
# click can launch/focus the running tray app. macOS registration is baked
|
|
39
|
+
# into Info.plist at build time from `plugins.deep-link` in tauri.conf.json;
|
|
40
|
+
# Windows/Linux register at runtime via `register_all()`.
|
|
41
|
+
tauri-plugin-deep-link = "2"
|
|
37
42
|
tauri-plugin-updater = "2"
|
|
38
43
|
tauri-plugin-process = "2"
|
|
39
44
|
tauri-plugin-autostart = "2.5.1"
|
|
@@ -48,6 +48,15 @@ use util::{
|
|
|
48
48
|
// should replace with their real icon.
|
|
49
49
|
pub(crate) const TRAY_PNG: &[u8] = include_bytes!("../icons/tray.png");
|
|
50
50
|
|
|
51
|
+
fn present_popover(app: &tauri::AppHandle) {
|
|
52
|
+
if let Some(window) = app.get_webview_window("popover") {
|
|
53
|
+
set_capture_included(&window);
|
|
54
|
+
configure_overlay_behavior(&window);
|
|
55
|
+
position_popover(app, &window);
|
|
56
|
+
present_interactive_window(&window);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
51
60
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
52
61
|
pub fn run() {
|
|
53
62
|
sentry_report::init();
|
|
@@ -57,12 +66,7 @@ pub fn run() {
|
|
|
57
66
|
// Second launch just focuses the popover of the already-running
|
|
58
67
|
// instance. Prevents the "two tray icons" UX where clicks fight
|
|
59
68
|
// over focus and neither popover shows.
|
|
60
|
-
|
|
61
|
-
set_capture_included(&window);
|
|
62
|
-
configure_overlay_behavior(&window);
|
|
63
|
-
position_popover(app, &window);
|
|
64
|
-
present_interactive_window(&window);
|
|
65
|
-
}
|
|
69
|
+
present_popover(app);
|
|
66
70
|
}))
|
|
67
71
|
.invoke_handler(tauri::generate_handler![
|
|
68
72
|
// clips commands
|
|
@@ -189,6 +193,7 @@ pub fn run() {
|
|
|
189
193
|
logfile::open_logs,
|
|
190
194
|
])
|
|
191
195
|
.plugin(tauri_plugin_shell::init())
|
|
196
|
+
.plugin(tauri_plugin_deep_link::init())
|
|
192
197
|
.plugin(tauri_plugin_process::init())
|
|
193
198
|
.plugin(tauri_plugin_fs::init())
|
|
194
199
|
.plugin(
|
|
@@ -236,6 +241,36 @@ pub fn run() {
|
|
|
236
241
|
err
|
|
237
242
|
})?;
|
|
238
243
|
|
|
244
|
+
// clips:// deep-link handler — a web "Open desktop app" click
|
|
245
|
+
// launches or focuses the running tray popover (same as a second
|
|
246
|
+
// launch). macOS registers the scheme via Info.plist at build time;
|
|
247
|
+
// Windows/Linux register it at runtime below.
|
|
248
|
+
{
|
|
249
|
+
use tauri_plugin_deep_link::DeepLinkExt;
|
|
250
|
+
let dl_handle = app.handle().clone();
|
|
251
|
+
app.deep_link().on_open_url(move |event| {
|
|
252
|
+
let urls: Vec<String> =
|
|
253
|
+
event.urls().iter().map(|u| u.to_string()).collect();
|
|
254
|
+
dlog!("[clips-tray] deep link opened: {:?}", urls);
|
|
255
|
+
present_popover(&dl_handle);
|
|
256
|
+
let _ = dl_handle.emit("clips:deep-link", urls);
|
|
257
|
+
});
|
|
258
|
+
#[cfg(any(windows, target_os = "linux"))]
|
|
259
|
+
if let Err(err) = app.deep_link().register_all() {
|
|
260
|
+
eprintln!("[clips-tray] deep link register_all failed: {err}");
|
|
261
|
+
}
|
|
262
|
+
// On Linux/Windows a cold launch from clips:// arrives as startup
|
|
263
|
+
// arguments rather than an on_open_url event. Consume it here so
|
|
264
|
+
// the popover appears and the browser does not hit its fallback.
|
|
265
|
+
if let Ok(Some(urls)) = app.deep_link().get_current() {
|
|
266
|
+
let url_strings: Vec<String> =
|
|
267
|
+
urls.iter().map(|u| u.to_string()).collect();
|
|
268
|
+
dlog!("[clips-tray] deep link cold start: {:?}", url_strings);
|
|
269
|
+
present_popover(app.handle());
|
|
270
|
+
let _ = app.handle().emit("clips:deep-link", url_strings);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
239
274
|
if let Err(err) = notifications::show_meeting_notification_window(app.handle()) {
|
|
240
275
|
println!("[clips-tray] show meeting notification failed: {err}");
|
|
241
276
|
}
|
|
@@ -39,6 +39,11 @@
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"plugins": {
|
|
42
|
+
"deep-link": {
|
|
43
|
+
"desktop": {
|
|
44
|
+
"schemes": ["clips"]
|
|
45
|
+
}
|
|
46
|
+
},
|
|
42
47
|
"shell": {
|
|
43
48
|
"open": "(((mailto:\\w+)|(tel:\\w+)|(https?://\\w+)).+|(zoommtg://([A-Za-z0-9-]+\\.)*zoom\\.us/join\\?.+)|(ms-settings:[A-Za-z0-9._?=&-]+))"
|
|
44
49
|
},
|
|
@@ -137,6 +137,37 @@ intent:
|
|
|
137
137
|
explicitly asks for a new artifact. Do not blindly submit another row merely
|
|
138
138
|
because a new Slack message arrived.
|
|
139
139
|
|
|
140
|
+
For a correction to an existing artifact, treat Slack history as identity and
|
|
141
|
+
intent context, not as the current record state. A title captured when the row
|
|
142
|
+
was created is a historical title: it may help locate the stable document ID,
|
|
143
|
+
but it is not authoritative after the row has been renamed in Content. Once the
|
|
144
|
+
stable ID is known, an external Slack or A2A correction must call
|
|
145
|
+
`pull-document` first to flush any open collaborative editor state; fail closed
|
|
146
|
+
if that flush/read cannot complete. Then read the canonical database row from
|
|
147
|
+
Content immediately before building the update. Treat those freshly read
|
|
148
|
+
values as authoritative for every field the correction does not explicitly
|
|
149
|
+
change.
|
|
150
|
+
|
|
151
|
+
Build corrections as sparse patches:
|
|
152
|
+
|
|
153
|
+
- Include only fields the user explicitly asks to change. "Keep," "preserve,"
|
|
154
|
+
"leave as is," and "unchanged" are constraints, not new values; omit those
|
|
155
|
+
fields from the mutation so a newer Content-side value cannot be overwritten
|
|
156
|
+
by stale Slack context.
|
|
157
|
+
- Omission and clearing are different operations. An omitted field keeps its
|
|
158
|
+
live Content value. Clear a field only when the user explicitly asks to
|
|
159
|
+
remove, unset, or clear it, and use the empty representation accepted by the
|
|
160
|
+
current database schema.
|
|
161
|
+
- Never reconstruct a full-row update from the original Slack request. Derive
|
|
162
|
+
the patch from the correction message and the freshly read canonical row,
|
|
163
|
+
while preserving the stable document ID.
|
|
164
|
+
- After the mutation, read the row again and verify that the requested fields
|
|
165
|
+
changed and the mutation did not include omitted fields. Post-write
|
|
166
|
+
verification is not compare-and-swap: it can reveal an unexpected result but
|
|
167
|
+
cannot prevent a concurrent edit between the read and a blind metadata or
|
|
168
|
+
property write. Report an action-provided conflict when one exists; otherwise
|
|
169
|
+
keep the patch minimal and do not claim the write was conflict-safe.
|
|
170
|
+
|
|
140
171
|
Apply people fields from verified identity and intent, not from convenient
|
|
141
172
|
guesswork:
|
|
142
173
|
|
|
@@ -47,6 +47,28 @@ iframe-backed screens on the infinite canvas.
|
|
|
47
47
|
and create one screen per URL/path. Shorthand like
|
|
48
48
|
`localhost:1234/onboarding/1` means `http://localhost:1234/onboarding/1`.
|
|
49
49
|
|
|
50
|
+
## Select And Reprompt
|
|
51
|
+
|
|
52
|
+
When a chat message begins with `[Reprompt selection]`, the selected subtree is
|
|
53
|
+
a hard write boundary. The only mutation path is `propose-node-rewrite` with
|
|
54
|
+
the exact `repromptId`, target, and `baseVersionHash` captured in
|
|
55
|
+
`design-reprompt-pending:<designId>:<fileId>`. Never use `apply-visual-edit`,
|
|
56
|
+
`apply-source-edit`, `write-source`, `write-local-file`, `edit-design`, or any
|
|
57
|
+
other content-writing action for that request. Clarifying questions are allowed,
|
|
58
|
+
but a requested change must remain a proposal.
|
|
59
|
+
|
|
60
|
+
Produce one variant by default. Produce two or three only when the instruction
|
|
61
|
+
asks for options. A retry includes `priorProposalId`; keep the same target and
|
|
62
|
+
base version, incorporate the feedback, and call `propose-node-rewrite` again.
|
|
63
|
+
The UI previews the returned subtree without persisting it.
|
|
64
|
+
|
|
65
|
+
Use `resolve-node-rewrite` for the accept/reject lifecycle. Accept applies the
|
|
66
|
+
chosen variant as one version-checked inline/Yjs content transaction so one
|
|
67
|
+
undo restores the prior structure; reject clears the proposal without changing
|
|
68
|
+
content. For conversational resolution such as “apply the second one,” call
|
|
69
|
+
`view-screen`, read the active `design.reprompt.proposal`, and pass its
|
|
70
|
+
`proposalId` plus the zero-based `variantIndex` to `resolve-node-rewrite`.
|
|
71
|
+
|
|
50
72
|
## Review Quality
|
|
51
73
|
|
|
52
74
|
- Treat the running app as the truth. Preserve its component language, tokens,
|
|
@@ -16,6 +16,13 @@ patterns live in `.agents/skills/`.
|
|
|
16
16
|
- Never hardcode API keys, tokens, webhook URLs, signing secrets, private Builder/internal data, customer data, or credential-looking literals. Use secrets/OAuth/runtime configuration and obvious placeholders in examples.
|
|
17
17
|
- Use the app actions for designs, files, versions, design systems, variants,
|
|
18
18
|
export, and sharing. Do not write design rows directly with SQL.
|
|
19
|
+
- A message beginning with `[Reprompt selection]` is preview-only. Call
|
|
20
|
+
`propose-node-rewrite` with its exact `repromptId`, target, and base hash;
|
|
21
|
+
never call `edit-design`, `update-design`, `update-file`, `generate-design`,
|
|
22
|
+
`apply-visual-edit`, or another content writer. Only the frontend-only
|
|
23
|
+
`resolve-node-rewrite` action may persist an explicitly accepted proposal.
|
|
24
|
+
- A message beginning with `[Selection question]` is read-only. Answer about
|
|
25
|
+
the captured element and subtree without calling content-writing actions.
|
|
19
26
|
- When a user wants an established public system as a starting point, call
|
|
20
27
|
`create-design-system` with `templateId: material-3`, `carbon-white`, or
|
|
21
28
|
`primer-light`. These are source-linked, versioned token snapshots with
|
|
@@ -268,6 +275,15 @@ patterns live in `.agents/skills/`.
|
|
|
268
275
|
generation planning state created by `generate-screens` (canvas region
|
|
269
276
|
assignments and per-frame instructions consumed by `generate-design` and
|
|
270
277
|
`view-screen`; not rendered as canvas overlays).
|
|
278
|
+
- `design-reprompt-pending:<designId>:<fileId>` is the client-captured source
|
|
279
|
+
selection, instruction, base hash, and authoritative current request id for
|
|
280
|
+
a scoped regenerate request.
|
|
281
|
+
- `design-reprompt-proposal:<designId>:<fileId>:<repromptId>` is one
|
|
282
|
+
request-specific preview-only subtree proposal. Candidate payloads have a
|
|
283
|
+
256 KiB aggregate serialized limit. Resolution and cancellation use atomic
|
|
284
|
+
compare-and-set cleanup so an older request cannot erase a newer one.
|
|
285
|
+
`view-screen` lists only proposals paired to the current pending request as
|
|
286
|
+
`pendingCandidateReviews`.
|
|
271
287
|
- `show-design-questions` opens focused pre-generation questions in the main
|
|
272
288
|
design canvas (`show-questions` application state).
|
|
273
289
|
- `guided-questions` may contain a one-click chat choice for the current
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
compareAndSetAppState,
|
|
4
|
+
readAppState,
|
|
5
|
+
} from "@agent-native/core/application-state";
|
|
6
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
designRepromptPendingStateKey,
|
|
11
|
+
designRepromptProposalStateKey,
|
|
12
|
+
isNodeRewriteProposal,
|
|
13
|
+
isPendingDesignReprompt,
|
|
14
|
+
} from "../shared/node-rewrite.js";
|
|
15
|
+
|
|
16
|
+
export default defineAction({
|
|
17
|
+
agentTool: false,
|
|
18
|
+
description:
|
|
19
|
+
"Cancel one pending node rewrite only when it is still the current request.",
|
|
20
|
+
schema: z.object({
|
|
21
|
+
designId: z.string().min(1),
|
|
22
|
+
fileId: z.string().min(1),
|
|
23
|
+
repromptId: z.string().min(1),
|
|
24
|
+
}),
|
|
25
|
+
run: async ({ designId, fileId, repromptId }) => {
|
|
26
|
+
await assertAccess("design", designId, "editor");
|
|
27
|
+
const pendingKey = designRepromptPendingStateKey(designId, fileId);
|
|
28
|
+
const pending = await readAppState(pendingKey);
|
|
29
|
+
if (
|
|
30
|
+
!isPendingDesignReprompt(pending) ||
|
|
31
|
+
pending.repromptId !== repromptId
|
|
32
|
+
) {
|
|
33
|
+
return { cancelled: false, superseded: true };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const proposalKey = designRepromptProposalStateKey(
|
|
37
|
+
designId,
|
|
38
|
+
fileId,
|
|
39
|
+
repromptId,
|
|
40
|
+
);
|
|
41
|
+
const proposal = await readAppState(proposalKey);
|
|
42
|
+
const [pendingCancelled, proposalCancelled] = await Promise.all([
|
|
43
|
+
compareAndSetAppState(pendingKey, pending, null),
|
|
44
|
+
isNodeRewriteProposal(proposal)
|
|
45
|
+
? compareAndSetAppState(proposalKey, proposal, null)
|
|
46
|
+
: Promise.resolve(false),
|
|
47
|
+
]);
|
|
48
|
+
return {
|
|
49
|
+
cancelled: pendingCancelled,
|
|
50
|
+
proposalCancelled,
|
|
51
|
+
superseded: !pendingCancelled,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
});
|