@agent-native/core 0.114.10 → 0.114.11
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 +6 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/extensions/actions.ts +219 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +19 -0
- package/corpus/templates/clips/app/components/player/video-player.tsx +48 -0
- package/corpus/templates/clips/app/lib/fmp4.ts +26 -4
- package/corpus/templates/clips/app/lib/mse-video-loader.ts +13 -2
- package/corpus/templates/clips/changelog/2026-07-20-rewind-capture-status.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +81 -71
- package/corpus/templates/clips/desktop/src/lib/rewind-status.ts +103 -0
- package/corpus/templates/clips/desktop/src/styles.css +10 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +193 -1
- package/dist/extensions/actions.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +19 -0
- package/package.json +1 -1
- package/src/extensions/actions.ts +219 -0
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +19 -0
|
@@ -86,6 +86,7 @@ import {
|
|
|
86
86
|
type RecorderStopResult,
|
|
87
87
|
} from "./lib/recorder";
|
|
88
88
|
import { REWIND_AGENT_PROMPT } from "./lib/rewind-agent-prompt";
|
|
89
|
+
import { getRewindStatusPresentation } from "./lib/rewind-status";
|
|
89
90
|
import {
|
|
90
91
|
loadBool,
|
|
91
92
|
loadString,
|
|
@@ -893,6 +894,7 @@ export function App() {
|
|
|
893
894
|
const [homeScreenMemoryStatus, setHomeScreenMemoryStatus] =
|
|
894
895
|
useState<ScreenMemoryStatus | null>(null);
|
|
895
896
|
const [homeScreenMemoryBusy, setHomeScreenMemoryBusy] = useState(false);
|
|
897
|
+
const homeScreenMemoryRefreshVersionRef = useRef(0);
|
|
896
898
|
const [rewindAgentPromptCopied, setRewindAgentPromptCopied] = useState(false);
|
|
897
899
|
const [agentHandoff, setAgentHandoff] =
|
|
898
900
|
useState<RewindAgentHandoffRequest | null>(null);
|
|
@@ -946,26 +948,54 @@ export function App() {
|
|
|
946
948
|
const isRecording = recorder !== null;
|
|
947
949
|
// Whether the popover window is shown; driven by the visibility effect below.
|
|
948
950
|
const [popoverVisible, setPopoverVisible] = useState(false);
|
|
951
|
+
const homeRewindPresentation = getRewindStatusPresentation({
|
|
952
|
+
status: homeScreenMemoryStatus,
|
|
953
|
+
config: featureConfig?.screenMemory ?? DEFAULT_SCREEN_MEMORY_CONFIG,
|
|
954
|
+
clipRecordingActive: isRecording || recordingFlowActive,
|
|
955
|
+
});
|
|
956
|
+
const refreshHomeScreenMemoryStatus = useCallback(() => {
|
|
957
|
+
const version = ++homeScreenMemoryRefreshVersionRef.current;
|
|
958
|
+
invoke<ScreenMemoryStatus>("screen_memory_status")
|
|
959
|
+
.then((status) => {
|
|
960
|
+
if (version === homeScreenMemoryRefreshVersionRef.current) {
|
|
961
|
+
setHomeScreenMemoryStatus(status);
|
|
962
|
+
}
|
|
963
|
+
})
|
|
964
|
+
.catch(() => {});
|
|
965
|
+
}, []);
|
|
949
966
|
useEffect(() => {
|
|
950
967
|
if (featureConfig?.screenMemory?.enabled !== true) {
|
|
968
|
+
homeScreenMemoryRefreshVersionRef.current += 1;
|
|
951
969
|
setHomeScreenMemoryStatus(null);
|
|
952
970
|
return;
|
|
953
971
|
}
|
|
954
972
|
let cancelled = false;
|
|
955
973
|
const refresh = () => {
|
|
956
|
-
|
|
957
|
-
.then((status) => {
|
|
958
|
-
if (!cancelled) setHomeScreenMemoryStatus(status);
|
|
959
|
-
})
|
|
960
|
-
.catch(() => {});
|
|
974
|
+
if (!cancelled) refreshHomeScreenMemoryStatus();
|
|
961
975
|
};
|
|
962
976
|
refresh();
|
|
963
977
|
const timer = window.setInterval(refresh, popoverVisible ? 5_000 : 30_000);
|
|
978
|
+
let unlisten: (() => void) | undefined;
|
|
979
|
+
listen("clips:screen-memory-changed", refresh)
|
|
980
|
+
.then((stopListening) => {
|
|
981
|
+
if (cancelled) {
|
|
982
|
+
stopListening();
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
unlisten = stopListening;
|
|
986
|
+
})
|
|
987
|
+
.catch(() => {});
|
|
964
988
|
return () => {
|
|
965
989
|
cancelled = true;
|
|
990
|
+
homeScreenMemoryRefreshVersionRef.current += 1;
|
|
966
991
|
window.clearInterval(timer);
|
|
992
|
+
unlisten?.();
|
|
967
993
|
};
|
|
968
|
-
}, [
|
|
994
|
+
}, [
|
|
995
|
+
featureConfig?.screenMemory?.enabled,
|
|
996
|
+
popoverVisible,
|
|
997
|
+
refreshHomeScreenMemoryStatus,
|
|
998
|
+
]);
|
|
969
999
|
const recordShortcutHandlerRef = useRef<() => void>(() => {});
|
|
970
1000
|
// Mirrors `bubbleActive` (assigned below once it is computed) so device
|
|
971
1001
|
// probes can synchronously tell whether the camera bubble owns the grant.
|
|
@@ -3154,8 +3184,11 @@ export function App() {
|
|
|
3154
3184
|
},
|
|
3155
3185
|
},
|
|
3156
3186
|
});
|
|
3157
|
-
|
|
3158
|
-
|
|
3187
|
+
// The native producer can take a moment to finish pausing. Do not keep
|
|
3188
|
+
// the Home switch locked while that status request waits; the existing
|
|
3189
|
+
// change event and bounded poll will reconcile it, and this best-effort
|
|
3190
|
+
// refresh can do the same without blocking the next resume action.
|
|
3191
|
+
refreshHomeScreenMemoryStatus();
|
|
3159
3192
|
} catch (err) {
|
|
3160
3193
|
console.error(
|
|
3161
3194
|
"[clips-tray] update Rewind remembering state failed:",
|
|
@@ -3633,34 +3666,13 @@ export function App() {
|
|
|
3633
3666
|
<section className="rewind-home-card" aria-label="Rewind status">
|
|
3634
3667
|
<div className="rewind-home-status">
|
|
3635
3668
|
<span
|
|
3636
|
-
className={`rewind-home-dot ${
|
|
3637
|
-
featureConfig?.screenMemory?.enabled === true &&
|
|
3638
|
-
featureConfig.screenMemory.paused !== true &&
|
|
3639
|
-
homeScreenMemoryStatus?.state === "recording"
|
|
3640
|
-
? "is-live"
|
|
3641
|
-
: ""
|
|
3642
|
-
}`}
|
|
3669
|
+
className={`rewind-home-dot ${homeRewindPresentation.isLive ? "is-live" : ""}`}
|
|
3643
3670
|
/>
|
|
3644
3671
|
<div>
|
|
3645
|
-
<strong>
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
? "Rewind is paused"
|
|
3650
|
-
: homeScreenMemoryStatus?.exclusionActive
|
|
3651
|
-
? "Rewind is protecting a private moment"
|
|
3652
|
-
: "Rewind is remembering"}
|
|
3653
|
-
</strong>
|
|
3654
|
-
{featureConfig?.screenMemory?.enabled !== true ||
|
|
3655
|
-
featureConfig.screenMemory.paused ||
|
|
3656
|
-
homeScreenMemoryStatus?.exclusionActive ? (
|
|
3657
|
-
<p>
|
|
3658
|
-
{featureConfig?.screenMemory?.enabled !== true
|
|
3659
|
-
? "Private memory for moments you may need later."
|
|
3660
|
-
: featureConfig.screenMemory.paused
|
|
3661
|
-
? "Existing local memory is still available."
|
|
3662
|
-
: "An excluded app is being skipped."}
|
|
3663
|
-
</p>
|
|
3672
|
+
<strong>{homeRewindPresentation.title}</strong>
|
|
3673
|
+
{!homeRewindPresentation.isLive ||
|
|
3674
|
+
homeRewindPresentation.hasError ? (
|
|
3675
|
+
<p>{homeRewindPresentation.detail}</p>
|
|
3664
3676
|
) : null}
|
|
3665
3677
|
</div>
|
|
3666
3678
|
<div className="rewind-home-controls">
|
|
@@ -4941,6 +4953,7 @@ function Setup({
|
|
|
4941
4953
|
);
|
|
4942
4954
|
const [screenMemoryStatus, setScreenMemoryStatus] =
|
|
4943
4955
|
useState<ScreenMemoryStatus | null>(null);
|
|
4956
|
+
const screenMemoryStatusRefreshVersionRef = useRef(0);
|
|
4944
4957
|
const [screenMemoryMessage, setScreenMemoryMessage] = useState<{
|
|
4945
4958
|
kind: "ok" | "error";
|
|
4946
4959
|
text: string;
|
|
@@ -4976,7 +4989,11 @@ function Setup({
|
|
|
4976
4989
|
(sum, segment) => sum + segment.bytes,
|
|
4977
4990
|
0,
|
|
4978
4991
|
);
|
|
4979
|
-
const
|
|
4992
|
+
const rewindStatusPresentation = getRewindStatusPresentation({
|
|
4993
|
+
status: screenMemoryStatus,
|
|
4994
|
+
config: screenMemory,
|
|
4995
|
+
clipRecordingActive: recordingActive,
|
|
4996
|
+
});
|
|
4980
4997
|
const captureControlsLocked = recordingActive;
|
|
4981
4998
|
|
|
4982
4999
|
useEffect(() => {
|
|
@@ -5172,11 +5189,16 @@ function Setup({
|
|
|
5172
5189
|
}
|
|
5173
5190
|
}
|
|
5174
5191
|
|
|
5175
|
-
|
|
5192
|
+
const refreshScreenMemoryStatus = useCallback(() => {
|
|
5193
|
+
const version = ++screenMemoryStatusRefreshVersionRef.current;
|
|
5176
5194
|
invoke<ScreenMemoryStatus>("screen_memory_status")
|
|
5177
|
-
.then(
|
|
5195
|
+
.then((status) => {
|
|
5196
|
+
if (version === screenMemoryStatusRefreshVersionRef.current) {
|
|
5197
|
+
setScreenMemoryStatus(status);
|
|
5198
|
+
}
|
|
5199
|
+
})
|
|
5178
5200
|
.catch(() => {});
|
|
5179
|
-
}
|
|
5201
|
+
}, []);
|
|
5180
5202
|
|
|
5181
5203
|
function refreshRewindEgressLog() {
|
|
5182
5204
|
invoke<RewindEgressEvent[]>("rewind_list_evidence_egress", { limit: 20 })
|
|
@@ -5257,6 +5279,7 @@ function Setup({
|
|
|
5257
5279
|
const status = await invoke<ScreenMemoryStatus>(
|
|
5258
5280
|
"screen_memory_delete_all",
|
|
5259
5281
|
);
|
|
5282
|
+
screenMemoryStatusRefreshVersionRef.current += 1;
|
|
5260
5283
|
setScreenMemoryStatus(status);
|
|
5261
5284
|
setScreenMemoryMessage({ kind: "ok", text: "Rewind cleared." });
|
|
5262
5285
|
} catch (err) {
|
|
@@ -5424,13 +5447,10 @@ function Setup({
|
|
|
5424
5447
|
useEffect(() => {
|
|
5425
5448
|
let cancelled = false;
|
|
5426
5449
|
const refresh = () => {
|
|
5427
|
-
|
|
5428
|
-
.then((status) => {
|
|
5429
|
-
if (!cancelled) setScreenMemoryStatus(status);
|
|
5430
|
-
})
|
|
5431
|
-
.catch(() => {});
|
|
5450
|
+
if (!cancelled) refreshScreenMemoryStatus();
|
|
5432
5451
|
};
|
|
5433
5452
|
refresh();
|
|
5453
|
+
const timer = window.setInterval(refresh, 5_000);
|
|
5434
5454
|
const unlistens: Array<() => void> = [];
|
|
5435
5455
|
const track = (p: Promise<() => void>) => {
|
|
5436
5456
|
p.then((u) => {
|
|
@@ -5448,6 +5468,8 @@ function Setup({
|
|
|
5448
5468
|
track(listen("clips:screen-memory-changed", refresh));
|
|
5449
5469
|
return () => {
|
|
5450
5470
|
cancelled = true;
|
|
5471
|
+
screenMemoryStatusRefreshVersionRef.current += 1;
|
|
5472
|
+
window.clearInterval(timer);
|
|
5451
5473
|
unlistens.forEach((u) => {
|
|
5452
5474
|
try {
|
|
5453
5475
|
u();
|
|
@@ -5456,7 +5478,7 @@ function Setup({
|
|
|
5456
5478
|
}
|
|
5457
5479
|
});
|
|
5458
5480
|
};
|
|
5459
|
-
}, []);
|
|
5481
|
+
}, [refreshScreenMemoryStatus]);
|
|
5460
5482
|
|
|
5461
5483
|
// Load model status on mount and keep it current via events.
|
|
5462
5484
|
useEffect(() => {
|
|
@@ -6044,17 +6066,16 @@ function Setup({
|
|
|
6044
6066
|
</div>
|
|
6045
6067
|
<div className="rewind-settings-status">
|
|
6046
6068
|
<span
|
|
6047
|
-
className={`rewind-home-dot ${
|
|
6069
|
+
className={`rewind-home-dot ${rewindStatusPresentation.isLive ? "is-live" : ""}`}
|
|
6048
6070
|
/>
|
|
6049
|
-
<span>
|
|
6050
|
-
{
|
|
6051
|
-
|
|
6052
|
-
|
|
6071
|
+
<span className="rewind-settings-status-copy">
|
|
6072
|
+
<strong>{rewindStatusPresentation.title}</strong>
|
|
6073
|
+
<span>
|
|
6074
|
+
{rewindStatusPresentation.kind === "recording" &&
|
|
6075
|
+
!rewindStatusPresentation.hasError
|
|
6053
6076
|
? `${screenMemorySegments.length} retained segment${screenMemorySegments.length === 1 ? "" : "s"} · ${formatStorageBytes(screenMemoryTotalBytes)}`
|
|
6054
|
-
:
|
|
6055
|
-
|
|
6056
|
-
: screenMemoryStatus?.lastError ||
|
|
6057
|
-
"Waiting for screen-recording permission."}
|
|
6077
|
+
: rewindStatusPresentation.detail}
|
|
6078
|
+
</span>
|
|
6058
6079
|
</span>
|
|
6059
6080
|
</div>
|
|
6060
6081
|
<div className="setup-section-heading">Privacy</div>
|
|
@@ -6550,17 +6571,11 @@ function Setup({
|
|
|
6550
6571
|
<div className="setup-section rewind-settings-entry">
|
|
6551
6572
|
<div className="rewind-settings-entry-main">
|
|
6552
6573
|
<span
|
|
6553
|
-
className={`rewind-home-dot ${
|
|
6574
|
+
className={`rewind-home-dot ${rewindStatusPresentation.isLive ? "is-live" : ""}`}
|
|
6554
6575
|
/>
|
|
6555
6576
|
<div>
|
|
6556
6577
|
<strong>Rewind</strong>
|
|
6557
|
-
<p className="setup-hint">
|
|
6558
|
-
{!screenMemory.enabled
|
|
6559
|
-
? "Off"
|
|
6560
|
-
: screenMemory.paused
|
|
6561
|
-
? "Paused · existing local memory is still available"
|
|
6562
|
-
: "Remembering locally"}
|
|
6563
|
-
</p>
|
|
6578
|
+
<p className="setup-hint">{rewindStatusPresentation.title}</p>
|
|
6564
6579
|
</div>
|
|
6565
6580
|
</div>
|
|
6566
6581
|
<button type="button" className="secondary" onClick={onOpenRewind}>
|
|
@@ -6738,21 +6753,16 @@ function Setup({
|
|
|
6738
6753
|
bundle ID cannot be detected.
|
|
6739
6754
|
</p>
|
|
6740
6755
|
<div className="whisper-status">
|
|
6741
|
-
{
|
|
6756
|
+
{rewindStatusPresentation.isLive ? (
|
|
6742
6757
|
<IconCircleCheck size={13} className="whisper-status-icon" />
|
|
6743
6758
|
) : (
|
|
6744
6759
|
<IconAlertTriangle size={13} className="whisper-status-icon" />
|
|
6745
6760
|
)}
|
|
6746
6761
|
<span>
|
|
6747
|
-
{
|
|
6748
|
-
|
|
6749
|
-
:
|
|
6750
|
-
|
|
6751
|
-
: screenMemory.paused
|
|
6752
|
-
? "Capture paused. Existing local coverage remains available."
|
|
6753
|
-
: screenMemoryStatus?.lastError
|
|
6754
|
-
? screenMemoryStatus.lastError
|
|
6755
|
-
: "Capture status is waiting for screen-recording permission."}
|
|
6762
|
+
{rewindStatusPresentation.kind === "recording" &&
|
|
6763
|
+
!rewindStatusPresentation.hasError
|
|
6764
|
+
? `Rewind is retaining local coverage: ${screenMemorySegments.length} segment${screenMemorySegments.length === 1 ? "" : "s"}, ${formatStorageBytes(screenMemoryTotalBytes)}.`
|
|
6765
|
+
: rewindStatusPresentation.detail}
|
|
6756
6766
|
</span>
|
|
6757
6767
|
</div>
|
|
6758
6768
|
{screenMemorySegments[0] ? (
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { ScreenMemoryConfig, ScreenMemoryStatus } from "../shared/config";
|
|
2
|
+
|
|
3
|
+
export type RewindPresentationKind =
|
|
4
|
+
| "off"
|
|
5
|
+
| "paused"
|
|
6
|
+
| "recording"
|
|
7
|
+
| "excluded"
|
|
8
|
+
| "idle"
|
|
9
|
+
| "error"
|
|
10
|
+
| "unavailable";
|
|
11
|
+
|
|
12
|
+
export interface RewindStatusPresentation {
|
|
13
|
+
kind: RewindPresentationKind;
|
|
14
|
+
title: string;
|
|
15
|
+
detail: string;
|
|
16
|
+
isLive: boolean;
|
|
17
|
+
hasError: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getRewindStatusPresentation({
|
|
21
|
+
status,
|
|
22
|
+
config,
|
|
23
|
+
clipRecordingActive = false,
|
|
24
|
+
}: {
|
|
25
|
+
status: ScreenMemoryStatus | null;
|
|
26
|
+
config: ScreenMemoryConfig;
|
|
27
|
+
clipRecordingActive?: boolean;
|
|
28
|
+
}): RewindStatusPresentation {
|
|
29
|
+
const runtimeConfig = status?.config ?? config;
|
|
30
|
+
|
|
31
|
+
if (!runtimeConfig.enabled || status?.state === "disabled") {
|
|
32
|
+
return {
|
|
33
|
+
kind: "off",
|
|
34
|
+
title: "Rewind is off",
|
|
35
|
+
detail: "Private memory for moments you may need later.",
|
|
36
|
+
isLive: false,
|
|
37
|
+
hasError: false,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (runtimeConfig.paused || status?.state === "paused") {
|
|
42
|
+
return {
|
|
43
|
+
kind: "paused",
|
|
44
|
+
title: "Rewind is paused",
|
|
45
|
+
detail: "Existing local memory is still available.",
|
|
46
|
+
isLive: false,
|
|
47
|
+
hasError: false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (status?.exclusionActive) {
|
|
52
|
+
return {
|
|
53
|
+
kind: "excluded",
|
|
54
|
+
title: "Rewind is protecting a private moment",
|
|
55
|
+
detail: status.coverage || "An excluded app is being skipped.",
|
|
56
|
+
isLive: false,
|
|
57
|
+
hasError: false,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (status?.available === false) {
|
|
62
|
+
return {
|
|
63
|
+
kind: "unavailable",
|
|
64
|
+
title: "Rewind is unavailable",
|
|
65
|
+
detail: "Rewind capture is not available on this device.",
|
|
66
|
+
isLive: false,
|
|
67
|
+
hasError: false,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (status?.state === "recording") {
|
|
72
|
+
return {
|
|
73
|
+
kind: "recording",
|
|
74
|
+
title: "Rewind is remembering",
|
|
75
|
+
detail:
|
|
76
|
+
status.lastError ||
|
|
77
|
+
status.coverage ||
|
|
78
|
+
"Rewind is retaining local coverage.",
|
|
79
|
+
isLive: true,
|
|
80
|
+
hasError: Boolean(status.lastError),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (status?.lastError) {
|
|
85
|
+
return {
|
|
86
|
+
kind: "error",
|
|
87
|
+
title: "Rewind needs attention",
|
|
88
|
+
detail: status.lastError,
|
|
89
|
+
isLive: false,
|
|
90
|
+
hasError: true,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
kind: "idle",
|
|
96
|
+
title: "Rewind is enabled but not currently capturing",
|
|
97
|
+
detail: clipRecordingActive
|
|
98
|
+
? "Rewind will resume when this Clip ends."
|
|
99
|
+
: "No new local coverage is being retained right now.",
|
|
100
|
+
isLive: false,
|
|
101
|
+
hasError: false,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -1707,6 +1707,16 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
1707
1707
|
margin: -4px 0 0;
|
|
1708
1708
|
}
|
|
1709
1709
|
|
|
1710
|
+
.rewind-settings-status-copy {
|
|
1711
|
+
display: grid;
|
|
1712
|
+
gap: 1px;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
.rewind-settings-status-copy strong {
|
|
1716
|
+
color: var(--fg);
|
|
1717
|
+
font-weight: 600;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1710
1720
|
.rewind-agent-row {
|
|
1711
1721
|
border-bottom: none;
|
|
1712
1722
|
}
|
|
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
62
62
|
error: string;
|
|
63
63
|
states?: undefined;
|
|
64
64
|
} | {
|
|
65
|
+
error?: undefined;
|
|
65
66
|
states: {
|
|
66
67
|
clientId: number;
|
|
67
68
|
state: string;
|
|
68
69
|
}[];
|
|
69
|
-
error?: undefined;
|
|
70
70
|
}>>;
|
|
71
71
|
/**
|
|
72
72
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -77,10 +77,10 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
77
77
|
error: string;
|
|
78
78
|
users?: undefined;
|
|
79
79
|
} | {
|
|
80
|
+
error?: undefined;
|
|
80
81
|
users: {
|
|
81
82
|
clientId: number;
|
|
82
83
|
lastSeen: number;
|
|
83
84
|
}[];
|
|
84
|
-
error?: undefined;
|
|
85
85
|
}>>;
|
|
86
86
|
//# sourceMappingURL=awareness.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"awareness.d.ts","sourceRoot":"","sources":["../../src/collab/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAOD,eAAO,MAAM,sBAAsB,EAAG,kBAA2B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,cAAc,GAAG,SAAS,GAChC,IAAI,CAON;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,KAAK,CAAC,EAAE,cAAc,GACrB,IAAI,CAgBN;AAoBD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAAmB,GAC7B,IAAI,CAON;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE1E;AAiBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAO1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,IAAI,CAOnE;AAkCD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa
|
|
1
|
+
{"version":3,"file":"awareness.d.ts","sourceRoot":"","sources":["../../src/collab/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAOD,eAAO,MAAM,sBAAsB,EAAG,kBAA2B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,cAAc,GAAG,SAAS,GAChC,IAAI,CAON;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,KAAK,CAAC,EAAE,cAAc,GACrB,IAAI,CAgBN;AAoBD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAAmB,GAC7B,IAAI,CAON;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE1E;AAiBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAO1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,IAAI,CAOnE;AAkCD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;;;;kBAwDa,MAAM;eAAS,MAAM;;GAoB1D,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;;kBAYM,MAAM;kBAAY,MAAM;;GAMvD,CAAC"}
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/extensions/actions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/extensions/actions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAkEhE,wBAAgB,4BAA4B,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAgyC1E"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER } from "../action-ui.js";
|
|
2
3
|
import { AgentActionStopError } from "../action.js";
|
|
3
4
|
import { writeAppState } from "../application-state/script-helpers.js";
|
|
5
|
+
import { getDbExec, isPostgres } from "../db/client.js";
|
|
4
6
|
import { readResource } from "../resources/script-helpers.js";
|
|
5
7
|
import { getRequestOrgId, getRequestRunContext, getRequestUserEmail, } from "../server/request-context.js";
|
|
6
8
|
import { resolveAccess } from "../sharing/access.js";
|
|
@@ -9,7 +11,7 @@ import { ExtensionContentEditError } from "./content-patch.js";
|
|
|
9
11
|
import { getLocalExtension, isLocalExtensionRow, listLocalExtensions, } from "./local.js";
|
|
10
12
|
import { extensionPath } from "./path.js";
|
|
11
13
|
import { addExtensionSlotTarget, installExtensionSlot, uninstallExtensionSlot, listExtensionsForSlot, listSlotsForExtension, } from "./slots/store.js";
|
|
12
|
-
import { createExtension, deleteExtension, findRecentDuplicateExtension, getHiddenExtensionIdsForCurrentUser, getExtension, getExtensionHistoryVersion, globalHideExtension, globalUnhideExtension, hideExtension, listExtensionHistory, listExtensions, restoreExtensionHistoryVersion, unhideExtension, updateExtension, updateExtensionContent, } from "./store.js";
|
|
14
|
+
import { createExtension, deleteExtension, ensureExtensionsTables, findRecentDuplicateExtension, getHiddenExtensionIdsForCurrentUser, getExtension, getExtensionHistoryVersion, globalHideExtension, globalUnhideExtension, hideExtension, listExtensionHistory, listExtensions, notifyExtensionChangeForResource, restoreExtensionHistoryVersion, unhideExtension, updateExtension, updateExtensionContent, } from "./store.js";
|
|
13
15
|
// A 200k extension body containing JSON-sensitive HTML/JS characters (quotes,
|
|
14
16
|
// backslashes, and newlines) expands to about 400k characters when pretty-JSON
|
|
15
17
|
// serialized by the agent loop. A history detail can carry the current and
|
|
@@ -689,6 +691,196 @@ export function createExtensionActionEntries() {
|
|
|
689
691
|
};
|
|
690
692
|
},
|
|
691
693
|
},
|
|
694
|
+
"extension-data-set": {
|
|
695
|
+
tool: {
|
|
696
|
+
description: "Write a value to an extension's extensionData store (the tool_data table) from the agent side. Use this when the agent needs to update or seed data that an extension reads via extensionData.get() at render time. Requires editor access to the extension.",
|
|
697
|
+
parameters: {
|
|
698
|
+
type: "object",
|
|
699
|
+
properties: {
|
|
700
|
+
extensionId: {
|
|
701
|
+
type: "string",
|
|
702
|
+
description: "Extension id whose data store to write to.",
|
|
703
|
+
},
|
|
704
|
+
collection: {
|
|
705
|
+
type: "string",
|
|
706
|
+
description: "Collection name within the extension's data store.",
|
|
707
|
+
},
|
|
708
|
+
itemId: {
|
|
709
|
+
type: "string",
|
|
710
|
+
description: "Item id within the collection. Upserts if it already exists.",
|
|
711
|
+
},
|
|
712
|
+
data: {
|
|
713
|
+
description: "The data value to store. Objects are JSON-serialized automatically.",
|
|
714
|
+
},
|
|
715
|
+
scope: {
|
|
716
|
+
type: "string",
|
|
717
|
+
enum: ["user", "org"],
|
|
718
|
+
description: "Storage scope. 'user' (default) is private to the current user; 'org' is shared across the organization.",
|
|
719
|
+
},
|
|
720
|
+
},
|
|
721
|
+
required: ["extensionId", "collection", "itemId", "data"],
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
run: async (args) => {
|
|
725
|
+
const extensionId = String(args?.extensionId ?? "").trim();
|
|
726
|
+
const collection = String(args?.collection ?? "").trim();
|
|
727
|
+
const itemId = String(args?.itemId ?? "").trim();
|
|
728
|
+
if (!extensionId || !collection || !itemId)
|
|
729
|
+
return "Error: extensionId, collection, and itemId are required.";
|
|
730
|
+
if (args?.data === undefined)
|
|
731
|
+
return "Error: data is required.";
|
|
732
|
+
await ensureExtensionsTables();
|
|
733
|
+
const access = await resolveAccess("extension", extensionId);
|
|
734
|
+
if (!access || access.role === "viewer")
|
|
735
|
+
return `Error: editor access required for extension ${extensionId}.`;
|
|
736
|
+
const userEmail = getRequestUserEmail()?.toLowerCase() ?? "";
|
|
737
|
+
const scope = args?.scope === "org" ? "org" : "user";
|
|
738
|
+
const orgId = getRequestOrgId();
|
|
739
|
+
if (scope === "org" && !orgId)
|
|
740
|
+
return "Error: org context required for scope=org.";
|
|
741
|
+
const data = typeof args.data === "string" ? args.data : JSON.stringify(args.data);
|
|
742
|
+
const MAX_BYTES = 1024 * 1024;
|
|
743
|
+
if (Buffer.byteLength(data, "utf8") > MAX_BYTES)
|
|
744
|
+
return `Error: data exceeds ${MAX_BYTES} byte limit. Store large payloads in file storage instead.`;
|
|
745
|
+
const now = new Date().toISOString();
|
|
746
|
+
const scopeKey = scope === "org" ? `org:${orgId}` : userEmail;
|
|
747
|
+
const client = getDbExec();
|
|
748
|
+
const pg = isPostgres();
|
|
749
|
+
const conflictClause = pg
|
|
750
|
+
? `ON CONFLICT (tool_id, collection, scope_key, item_id)
|
|
751
|
+
DO UPDATE SET data = EXCLUDED.data, updated_at = EXCLUDED.updated_at`
|
|
752
|
+
: `ON CONFLICT (tool_id, collection, scope_key, item_id)
|
|
753
|
+
DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`;
|
|
754
|
+
await client.execute({
|
|
755
|
+
sql: `UPDATE tools SET updated_at = ? WHERE id = ?`,
|
|
756
|
+
args: [now, extensionId],
|
|
757
|
+
});
|
|
758
|
+
await client.execute({
|
|
759
|
+
sql: `INSERT INTO tool_data (id, tool_id, collection, item_id, data, owner_email, scope, org_id, scope_key, created_at, updated_at)
|
|
760
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
761
|
+
${conflictClause}`,
|
|
762
|
+
args: [
|
|
763
|
+
randomUUID(),
|
|
764
|
+
extensionId,
|
|
765
|
+
collection,
|
|
766
|
+
itemId,
|
|
767
|
+
data,
|
|
768
|
+
userEmail,
|
|
769
|
+
scope,
|
|
770
|
+
scope === "org" ? orgId : null,
|
|
771
|
+
scopeKey,
|
|
772
|
+
now,
|
|
773
|
+
now,
|
|
774
|
+
],
|
|
775
|
+
});
|
|
776
|
+
await notifyExtensionChangeForResource(extensionId);
|
|
777
|
+
return {
|
|
778
|
+
ok: true,
|
|
779
|
+
id: itemId,
|
|
780
|
+
extensionId,
|
|
781
|
+
collection,
|
|
782
|
+
scope,
|
|
783
|
+
updatedAt: now,
|
|
784
|
+
};
|
|
785
|
+
},
|
|
786
|
+
},
|
|
787
|
+
"extension-data-get": {
|
|
788
|
+
tool: {
|
|
789
|
+
description: "Read items from an extension's extensionData store (the tool_data table) from the agent side. Use this to inspect what data an extension currently has stored, or to verify a write succeeded. Requires viewer access to the extension.",
|
|
790
|
+
parameters: {
|
|
791
|
+
type: "object",
|
|
792
|
+
properties: {
|
|
793
|
+
extensionId: {
|
|
794
|
+
type: "string",
|
|
795
|
+
description: "Extension id whose data store to read from.",
|
|
796
|
+
},
|
|
797
|
+
collection: {
|
|
798
|
+
type: "string",
|
|
799
|
+
description: "Collection name within the extension's data store.",
|
|
800
|
+
},
|
|
801
|
+
itemId: {
|
|
802
|
+
type: "string",
|
|
803
|
+
description: "Optional specific item id. If omitted, lists all items in the collection.",
|
|
804
|
+
},
|
|
805
|
+
scope: {
|
|
806
|
+
type: "string",
|
|
807
|
+
enum: ["user", "org", "all"],
|
|
808
|
+
description: "Storage scope to read. 'user' (default) reads the current user's items; 'org' reads organization-shared items; 'all' reads both.",
|
|
809
|
+
},
|
|
810
|
+
limit: {
|
|
811
|
+
type: "number",
|
|
812
|
+
description: "Maximum items to return when listing (default 100, max 1000).",
|
|
813
|
+
},
|
|
814
|
+
},
|
|
815
|
+
required: ["extensionId", "collection"],
|
|
816
|
+
},
|
|
817
|
+
},
|
|
818
|
+
run: async (args) => {
|
|
819
|
+
const extensionId = String(args?.extensionId ?? "").trim();
|
|
820
|
+
const collection = String(args?.collection ?? "").trim();
|
|
821
|
+
if (!extensionId || !collection)
|
|
822
|
+
return "Error: extensionId and collection are required.";
|
|
823
|
+
await ensureExtensionsTables();
|
|
824
|
+
const access = await resolveAccess("extension", extensionId);
|
|
825
|
+
if (!access)
|
|
826
|
+
return `Error: no access to extension ${extensionId}.`;
|
|
827
|
+
const userEmail = getRequestUserEmail()?.toLowerCase() ?? "";
|
|
828
|
+
const scope = args?.scope ?? "user";
|
|
829
|
+
const orgId = getRequestOrgId();
|
|
830
|
+
if ((scope === "org" || scope === "all") && !orgId)
|
|
831
|
+
return "Error: org context required for scope=org or scope=all.";
|
|
832
|
+
const itemId = args?.itemId ? String(args.itemId).trim() : null;
|
|
833
|
+
const limit = Math.min(Math.max(1, Number(args?.limit) || 100), 1000);
|
|
834
|
+
const client = getDbExec();
|
|
835
|
+
if (itemId) {
|
|
836
|
+
const scopeClause = scope === "org"
|
|
837
|
+
? `AND scope = 'org' AND org_id = ?`
|
|
838
|
+
: scope === "all"
|
|
839
|
+
? `AND ((scope = 'user' AND lower(owner_email) = ?) OR (scope = 'org' AND org_id = ?))`
|
|
840
|
+
: `AND scope = 'user' AND lower(owner_email) = ?`;
|
|
841
|
+
const scopeArgs = scope === "org"
|
|
842
|
+
? [orgId ?? ""]
|
|
843
|
+
: scope === "all"
|
|
844
|
+
? [userEmail, orgId ?? ""]
|
|
845
|
+
: [userEmail];
|
|
846
|
+
const result = await client.execute({
|
|
847
|
+
sql: `SELECT COALESCE(item_id, id) AS id, data, scope, owner_email, updated_at
|
|
848
|
+
FROM tool_data
|
|
849
|
+
WHERE tool_id = ? AND collection = ? AND COALESCE(item_id, id) = ? ${scopeClause}`,
|
|
850
|
+
args: [extensionId, collection, itemId, ...scopeArgs],
|
|
851
|
+
});
|
|
852
|
+
const row = result.rows?.[0];
|
|
853
|
+
if (!row)
|
|
854
|
+
return { found: false, extensionId, collection, itemId };
|
|
855
|
+
return { found: true, ...row };
|
|
856
|
+
}
|
|
857
|
+
const scopeClause = scope === "org"
|
|
858
|
+
? `AND scope = 'org' AND org_id = ?`
|
|
859
|
+
: scope === "all"
|
|
860
|
+
? `AND ((scope = 'user' AND lower(owner_email) = ?) OR (scope = 'org' AND org_id = ?))`
|
|
861
|
+
: `AND scope = 'user' AND lower(owner_email) = ?`;
|
|
862
|
+
const scopeArgs = scope === "org"
|
|
863
|
+
? [orgId ?? ""]
|
|
864
|
+
: scope === "all"
|
|
865
|
+
? [userEmail, orgId ?? ""]
|
|
866
|
+
: [userEmail];
|
|
867
|
+
const result = await client.execute({
|
|
868
|
+
sql: `SELECT COALESCE(item_id, id) AS id, data, scope, owner_email, updated_at
|
|
869
|
+
FROM tool_data
|
|
870
|
+
WHERE tool_id = ? AND collection = ? ${scopeClause}
|
|
871
|
+
ORDER BY updated_at DESC
|
|
872
|
+
LIMIT ?`,
|
|
873
|
+
args: [extensionId, collection, ...scopeArgs, limit],
|
|
874
|
+
});
|
|
875
|
+
return {
|
|
876
|
+
extensionId,
|
|
877
|
+
collection,
|
|
878
|
+
scope,
|
|
879
|
+
count: result.rows?.length ?? 0,
|
|
880
|
+
items: result.rows ?? [],
|
|
881
|
+
};
|
|
882
|
+
},
|
|
883
|
+
},
|
|
692
884
|
"delete-extension": {
|
|
693
885
|
tool: {
|
|
694
886
|
description: "Permanently delete an extension everywhere it is shared. Requires owner/admin access. If the user only wants a shared extension removed from their own sidebar/list, use hide-extension instead.",
|