@agent-native/core 0.172.1 → 0.172.3

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.
@@ -1177,6 +1177,8 @@ export function App({
1177
1177
  const [meetings, setMeetings] = useState<PopoverMeeting[]>([]);
1178
1178
  const [meetingsLoading, setMeetingsLoading] = useState(false);
1179
1179
  const [meetingsError, setMeetingsError] = useState<string | null>(null);
1180
+ const [meetingsCalendarNeedsReauth, setMeetingsCalendarNeedsReauth] =
1181
+ useState(false);
1180
1182
  const [meetingStartMessage, setMeetingStartMessage] = useState<string | null>(
1181
1183
  null,
1182
1184
  );
@@ -1974,17 +1976,29 @@ export function App({
1974
1976
  if (authStatus !== "authed") {
1975
1977
  setMeetings([]);
1976
1978
  setMeetingsError(null);
1979
+ setMeetingsCalendarNeedsReauth(false);
1977
1980
  return;
1978
1981
  }
1979
1982
 
1980
1983
  setMeetingsLoading(true);
1981
1984
  setMeetingsError(null);
1982
1985
  try {
1983
- const result = await callClipsAction<{ meetings?: unknown[] }>(
1986
+ const result = await callClipsAction<{
1987
+ meetings?: unknown[];
1988
+ calendarErrors?: Array<{ needsReauth?: boolean }>;
1989
+ }>(
1984
1990
  "list-meetings",
1985
- { view: "upcoming", limit: 3, upcomingWithinMin: 24 * 60 },
1991
+ {
1992
+ view: "upcoming",
1993
+ limit: 3,
1994
+ upcomingWithinMin: 24 * 60,
1995
+ },
1986
1996
  { method: "GET" },
1987
1997
  );
1998
+ setMeetingsCalendarNeedsReauth(
1999
+ result.calendarErrors?.some((error) => error.needsReauth === true) ===
2000
+ true,
2001
+ );
1988
2002
  const list = Array.isArray(result.meetings) ? result.meetings : [];
1989
2003
  setMeetings(
1990
2004
  list.slice(0, 3).map((raw) => {
@@ -2002,6 +2016,7 @@ export function App({
2002
2016
  );
2003
2017
  } catch (err) {
2004
2018
  setMeetings([]);
2019
+ setMeetingsCalendarNeedsReauth(false);
2005
2020
  setMeetingsError(
2006
2021
  err instanceof Error ? err.message : "Could not load meetings.",
2007
2022
  );
@@ -4150,6 +4165,7 @@ export function App({
4150
4165
  onStartNotes={startMeetingNotes}
4151
4166
  onStartNotesAndJoin={startMeetingNotesAndJoin}
4152
4167
  onShowActiveMeeting={showActiveMeetingPill}
4168
+ calendarNeedsReauth={meetingsCalendarNeedsReauth}
4153
4169
  />
4154
4170
  </div>
4155
4171
  );
@@ -5409,6 +5425,7 @@ function MeetingsPopoverView({
5409
5425
  onStartNotes,
5410
5426
  onStartNotesAndJoin,
5411
5427
  onShowActiveMeeting,
5428
+ calendarNeedsReauth,
5412
5429
  }: {
5413
5430
  meetings: PopoverMeeting[];
5414
5431
  loading: boolean;
@@ -5431,23 +5448,8 @@ function MeetingsPopoverView({
5431
5448
  includeFromMeetingStart?: boolean,
5432
5449
  ) => void;
5433
5450
  onShowActiveMeeting: (meetingId: string) => void;
5451
+ calendarNeedsReauth: boolean;
5434
5452
  }) {
5435
- const [includeHistoryFor, setIncludeHistoryFor] = useState<Set<string>>(
5436
- () => new Set(),
5437
- );
5438
-
5439
- const consumeIncludeHistoryChoice = (meeting: PopoverMeeting): boolean => {
5440
- const include = includeHistoryFor.has(meeting.id);
5441
- // This is intentionally a one-shot choice. It never follows the next
5442
- // meeting, an automatic start, or a tray action around like a lost duck.
5443
- setIncludeHistoryFor((current) => {
5444
- const next = new Set(current);
5445
- next.delete(meeting.id);
5446
- return next;
5447
- });
5448
- return include;
5449
- };
5450
-
5451
5453
  return (
5452
5454
  <div className="setup popover-view">
5453
5455
  <PopoverSubViewHeader
@@ -5483,9 +5485,21 @@ function MeetingsPopoverView({
5483
5485
  <div className="popover-empty-card">
5484
5486
  <strong>Could not load meetings</strong>
5485
5487
  <p>{error}</p>
5488
+ {calendarNeedsReauth ? (
5489
+ <p>Reconnect your calendar from the Meetings page.</p>
5490
+ ) : null}
5486
5491
  <button type="button" className="secondary" onClick={onRefresh}>
5487
5492
  Try again
5488
5493
  </button>
5494
+ {calendarNeedsReauth ? (
5495
+ <button
5496
+ type="button"
5497
+ className="secondary"
5498
+ onClick={onOpenMeetings}
5499
+ >
5500
+ Open Meetings
5501
+ </button>
5502
+ ) : null}
5489
5503
  </div>
5490
5504
  ) : loading ? (
5491
5505
  <div className="popover-empty-card">
@@ -5494,8 +5508,16 @@ function MeetingsPopoverView({
5494
5508
  </div>
5495
5509
  ) : meetings.length === 0 ? (
5496
5510
  <div className="popover-empty-card">
5497
- <strong>No meetings ready</strong>
5498
- <p>Connect Google Calendar or open the Meetings page to see setup.</p>
5511
+ <strong>
5512
+ {calendarNeedsReauth
5513
+ ? "Reconnect your calendar"
5514
+ : "No meetings ready"}
5515
+ </strong>
5516
+ <p>
5517
+ {calendarNeedsReauth
5518
+ ? "Your calendar connection needs attention before Clips can match meeting titles."
5519
+ : "Connect Google Calendar or open the Meetings page to see setup."}
5520
+ </p>
5499
5521
  <button type="button" className="secondary" onClick={onOpenMeetings}>
5500
5522
  Open Meetings
5501
5523
  </button>
@@ -5507,7 +5529,6 @@ function MeetingsPopoverView({
5507
5529
  const hasJoin = Boolean(meeting.joinUrl);
5508
5530
  const isActive = activeMeetingId === meeting.id;
5509
5531
  const rewindHistory = rewindHistoryAvailability[meeting.id];
5510
- const includeHistory = includeHistoryFor.has(meeting.id);
5511
5532
  return (
5512
5533
  <div className="popover-list-item" key={meeting.id}>
5513
5534
  <div className="popover-list-icon">
@@ -5519,24 +5540,6 @@ function MeetingsPopoverView({
5519
5540
  {formatMeetingWhen(meeting)}
5520
5541
  {meeting.platform ? ` · ${meeting.platform}` : ""}
5521
5542
  </div>
5522
- {canStart && rewindHistory?.available ? (
5523
- <label className="popover-list-sub">
5524
- <input
5525
- type="checkbox"
5526
- checked={includeHistory}
5527
- onChange={(event) => {
5528
- const checked = event.currentTarget.checked;
5529
- setIncludeHistoryFor((current) => {
5530
- const next = new Set(current);
5531
- if (checked) next.add(meeting.id);
5532
- else next.delete(meeting.id);
5533
- return next;
5534
- });
5535
- }}
5536
- />{" "}
5537
- Include from meeting start
5538
- </label>
5539
- ) : null}
5540
5543
  </div>
5541
5544
  {isActive ? (
5542
5545
  <button
@@ -5556,11 +5559,11 @@ function MeetingsPopoverView({
5556
5559
  hasJoin
5557
5560
  ? onStartNotesAndJoin(
5558
5561
  meeting,
5559
- consumeIncludeHistoryChoice(meeting),
5562
+ rewindHistory?.available === true,
5560
5563
  )
5561
5564
  : onStartNotes(
5562
5565
  meeting,
5563
- consumeIncludeHistoryChoice(meeting),
5566
+ rewindHistory?.available === true,
5564
5567
  )
5565
5568
  }
5566
5569
  title={
@@ -5850,6 +5853,8 @@ function Setup({
5850
5853
  featureConfig?.screenMemory ?? DEFAULT_SCREEN_MEMORY_CONFIG;
5851
5854
  const [screenMemory, setScreenMemory] = useState(observedScreenMemory);
5852
5855
  const [rewindConsentOpen, setRewindConsentOpen] = useState(false);
5856
+ const [rewindManageOpen, setRewindManageOpen] = useState(false);
5857
+ const [rewindShowAdvanced, setRewindShowAdvanced] = useState(false);
5853
5858
  const screenMemoryRef = useRef(observedScreenMemory);
5854
5859
  const screenMemoryMutationRef = useRef(0);
5855
5860
  const screenMemoryMutationVersionRef = useRef(0);
@@ -6969,31 +6974,11 @@ function Setup({
6969
6974
  );
6970
6975
  }
6971
6976
 
6972
- /**
6973
- * Rewind's settings, flat. Every one of these used to live behind a Manage
6974
- * button inside a popover launched from another popover — three layers deep,
6975
- * with tooltips escaping behind the window. They are ordinary rows now, in
6976
- * the order a user reasons about them: does it run, what does it keep, who
6977
- * may see it, where does it live.
6978
- */
6979
6977
  function renderRewindSettings() {
6980
6978
  const rewindOn = screenMemory.enabled === true;
6981
6979
  return (
6982
6980
  <div className="mx-auto grid w-full max-w-[620px] gap-7 pb-4">
6983
6981
  <SettingsGroup>
6984
- {/* A confirmation, so a dialog rather than a takeover: the user is
6985
- answering one question about the screen behind it, not moving to
6986
- a new place. What it remembers is chosen afterwards, in the row
6987
- below — asking before they have agreed puts the options in front
6988
- of the decision.
6989
-
6990
- The copy names what is captured and what can leave, and nothing
6991
- else. Two claims are tempting and both are false: this buffer
6992
- holds screen *video* (hence the GB disk limit below), not app
6993
- and window notes; and it cannot promise "never leaves this Mac"
6994
- because the agent-handoff path uploads an approved range. A
6995
- consent screen is the one place a comforting simplification is
6996
- indistinguishable from a lie. */}
6997
6982
  <UiAlertDialog
6998
6983
  open={rewindConsentOpen}
6999
6984
  onOpenChange={setRewindConsentOpen}
@@ -7038,24 +7023,397 @@ function Setup({
7038
7023
  label="Rewind"
7039
7024
  description="Keeps a rolling record of your recent screen on this device"
7040
7025
  control={
7041
- <SettingsSwitch
7042
- checked={rewindOn}
7043
- onCheckedChange={(next) => {
7044
- // Turning it on starts continuously capturing the screen, so
7045
- // it routes through consent rather than flipping silently.
7046
- // Turning it off needs no confirmation — stopping is safe.
7047
- if (next) {
7048
- setRewindConsentOpen(true);
7049
- return;
7026
+ <div className="flex items-center gap-2">
7027
+ <SettingsPopover
7028
+ title="Rewind settings"
7029
+ open={rewindManageOpen}
7030
+ onOpenChange={setRewindManageOpen}
7031
+ className="w-[480px] max-w-[calc(100vw-24px)]"
7032
+ trigger={
7033
+ <SettingsActionButton
7034
+ aria-expanded={rewindManageOpen}
7035
+ aria-haspopup="dialog"
7036
+ >
7037
+ Manage
7038
+ </SettingsActionButton>
7050
7039
  }
7051
- void setScreenMemoryConfig({ enabled: false });
7052
- }}
7053
- /* Rust rejects Rewind capture changes mid-Clip
7054
- (config.rs `set_feature_config` guard); disabling here
7055
- turns that hard error into a visible lock. */
7056
- disabled={screenMemoryConfigBusy || captureControlsLocked}
7057
- label="Rewind"
7058
- />
7040
+ >
7041
+ <div className="grid max-h-[min(620px,calc(100vh-80px))] gap-3 overflow-y-auto pr-1">
7042
+ {rewindOn ? (
7043
+ <>
7044
+ <SettingsGroup label="Capture">
7045
+ <SettingsRow
7046
+ label="Remember"
7047
+ description="Choose what Rewind captures"
7048
+ control={
7049
+ <SettingsSelect
7050
+ ariaLabel="What Rewind remembers"
7051
+ value={screenMemory.captureMode ?? "visuals"}
7052
+ onValueChange={(value) => {
7053
+ void setScreenMemoryConfig({
7054
+ captureMode: value as RewindCaptureMode,
7055
+ });
7056
+ }}
7057
+ disabled={
7058
+ screenMemoryConfigBusy ||
7059
+ captureControlsLocked
7060
+ }
7061
+ options={[
7062
+ { value: "visuals", label: "Screen only" },
7063
+ {
7064
+ value: "visuals-audio",
7065
+ label: "Screen + audio",
7066
+ },
7067
+ ]}
7068
+ />
7069
+ }
7070
+ />
7071
+ <SettingsRow
7072
+ label="Time limit"
7073
+ description="Choose how long Rewind keeps your screen history"
7074
+ control={
7075
+ <SettingsSelect
7076
+ ariaLabel="Rewind time limit"
7077
+ placeholder={`${screenMemory.retentionHours} hours`}
7078
+ value={String(screenMemory.retentionHours)}
7079
+ onValueChange={(value) => {
7080
+ void setScreenMemoryConfig({
7081
+ retentionHours: Number(value),
7082
+ });
7083
+ }}
7084
+ disabled={screenMemoryConfigBusy}
7085
+ options={[
7086
+ { value: "8", label: "8 hours" },
7087
+ { value: "24", label: "24 hours" },
7088
+ ]}
7089
+ />
7090
+ }
7091
+ />
7092
+ <SettingsRow
7093
+ label="Storage limit"
7094
+ description="Choose how much space Rewind can use on this device"
7095
+ control={
7096
+ <SettingsSelect
7097
+ ariaLabel="Rewind storage limit"
7098
+ placeholder={formatStorageBytes(
7099
+ screenMemory.maxBytes,
7100
+ )}
7101
+ value={String(screenMemory.maxBytes)}
7102
+ onValueChange={(value) => {
7103
+ void setScreenMemoryConfig({
7104
+ maxBytes: Number(value),
7105
+ });
7106
+ }}
7107
+ disabled={screenMemoryConfigBusy}
7108
+ options={[
7109
+ {
7110
+ value: String(5 * 1024 * 1024 * 1024),
7111
+ label: "5 GB",
7112
+ },
7113
+ {
7114
+ value: String(20 * 1024 * 1024 * 1024),
7115
+ label: "20 GB",
7116
+ },
7117
+ {
7118
+ value: String(50 * 1024 * 1024 * 1024),
7119
+ label: "50 GB",
7120
+ },
7121
+ ]}
7122
+ />
7123
+ }
7124
+ />
7125
+ </SettingsGroup>
7126
+ <SettingsActionButton
7127
+ emphasis="quiet"
7128
+ className="justify-self-start"
7129
+ aria-expanded={rewindShowAdvanced}
7130
+ onClick={() =>
7131
+ setRewindShowAdvanced((current) => !current)
7132
+ }
7133
+ >
7134
+ {rewindShowAdvanced
7135
+ ? "Hide advanced"
7136
+ : "Show advanced"}
7137
+ </SettingsActionButton>
7138
+ {rewindShowAdvanced ? (
7139
+ <>
7140
+ <SettingsGroup label="Privacy">
7141
+ <SettingsRow
7142
+ label="Excluded apps"
7143
+ description="Apps Rewind never captures"
7144
+ control={
7145
+ <SettingsActionButton
7146
+ onClick={() =>
7147
+ void chooseExcludedApplications()
7148
+ }
7149
+ disabled={excludedAppsBusy}
7150
+ >
7151
+ Choose apps
7152
+ </SettingsActionButton>
7153
+ }
7154
+ >
7155
+ {excludedAppGroups.length > 0 ? (
7156
+ <div className="grid gap-1">
7157
+ {excludedAppGroups.map((app) => (
7158
+ <div
7159
+ key={app.bundleIds.join(",")}
7160
+ className="flex items-center justify-between gap-2"
7161
+ >
7162
+ <span className="truncate">
7163
+ {app.name}
7164
+ </span>
7165
+ <SettingsActionButton
7166
+ emphasis="quiet"
7167
+ onClick={() =>
7168
+ removeExcludedApplications(
7169
+ app.bundleIds,
7170
+ )
7171
+ }
7172
+ >
7173
+ Remove
7174
+ </SettingsActionButton>
7175
+ </div>
7176
+ ))}
7177
+ </div>
7178
+ ) : null}
7179
+ </SettingsRow>
7180
+ <SettingsRow
7181
+ label="Review before sending"
7182
+ description="Approve each visual or audio range before an agent receives it"
7183
+ control={
7184
+ <SettingsSwitch
7185
+ checked={
7186
+ screenMemory.reviewBeforeSending !== false
7187
+ }
7188
+ onCheckedChange={(next) => {
7189
+ void setScreenMemoryConfig({
7190
+ reviewBeforeSending: next,
7191
+ });
7192
+ }}
7193
+ disabled={screenMemoryConfigBusy}
7194
+ label="Review before sending"
7195
+ />
7196
+ }
7197
+ />
7198
+ <SettingsRow
7199
+ label="Preview before sending"
7200
+ description="Open the range locally so you see exactly what is sent"
7201
+ control={
7202
+ <SettingsSwitch
7203
+ checked={
7204
+ screenMemory.autoPreviewBeforeSending ===
7205
+ true
7206
+ }
7207
+ onCheckedChange={(next) => {
7208
+ void setScreenMemoryConfig({
7209
+ autoPreviewBeforeSending: next,
7210
+ });
7211
+ }}
7212
+ disabled={screenMemoryConfigBusy}
7213
+ label="Preview before sending"
7214
+ />
7215
+ }
7216
+ />
7217
+ <SettingsRow
7218
+ label="Keep agent Clips"
7219
+ description="Choose how long Clips made for agents stay in your library"
7220
+ control={
7221
+ <SettingsSelect
7222
+ ariaLabel="How long agent-created Clips are kept"
7223
+ value={screenMemory.agentClipRetention}
7224
+ onValueChange={(value) => {
7225
+ void setScreenMemoryConfig({
7226
+ agentClipRetention:
7227
+ value as ScreenMemoryStatus["config"]["agentClipRetention"],
7228
+ });
7229
+ }}
7230
+ disabled={screenMemoryConfigBusy}
7231
+ options={[
7232
+ {
7233
+ value: "forever",
7234
+ label: "Forever",
7235
+ },
7236
+ {
7237
+ value: "24-hours",
7238
+ label: "24 hours",
7239
+ },
7240
+ {
7241
+ value: "7-days",
7242
+ label: "7 days",
7243
+ },
7244
+ {
7245
+ value: "30-days",
7246
+ label: "30 days",
7247
+ },
7248
+ ]}
7249
+ />
7250
+ }
7251
+ />
7252
+ <SettingsRow
7253
+ label="Agent activity"
7254
+ description="Each time an agent searched this device's memory, newest first"
7255
+ >
7256
+ {rewindEgressEvents.length === 0 ? (
7257
+ <p>No agent has searched it yet.</p>
7258
+ ) : (
7259
+ <div className="grid gap-1">
7260
+ {rewindEgressEvents
7261
+ .slice(0, 10)
7262
+ .map((event) => (
7263
+ <div
7264
+ key={`${event.requestId}-${event.state}`}
7265
+ className="flex items-center justify-between gap-2"
7266
+ >
7267
+ <span className="truncate">
7268
+ {new Date(
7269
+ event.occurredAt,
7270
+ ).toLocaleString()}
7271
+ </span>
7272
+ <span className="shrink-0">
7273
+ {event.state} ·{" "}
7274
+ {`${event.evidenceCount} item${event.evidenceCount === 1 ? "" : "s"}`}
7275
+ </span>
7276
+ </div>
7277
+ ))}
7278
+ </div>
7279
+ )}
7280
+ </SettingsRow>
7281
+ </SettingsGroup>
7282
+
7283
+ <SettingsGroup label="Agents">
7284
+ <SettingsRow
7285
+ label="Setup prompt"
7286
+ description="Paste it into an agent once to install Rewind's instructions"
7287
+ control={
7288
+ <>
7289
+ <SettingsActionButton
7290
+ emphasis="quiet"
7291
+ onClick={onOpenRewindDocs}
7292
+ >
7293
+ Learn more
7294
+ </SettingsActionButton>
7295
+ <SettingsActionButton
7296
+ onClick={onCopyRewindAgentPrompt}
7297
+ >
7298
+ {rewindAgentPromptCopied
7299
+ ? "Copied"
7300
+ : "Copy"}
7301
+ </SettingsActionButton>
7302
+ </>
7303
+ }
7304
+ />
7305
+ <SettingsRow
7306
+ label="Connect an agent"
7307
+ description="Gives a local agent access to this device's Rewind memory"
7308
+ control={
7309
+ <>
7310
+ <SettingsActionButton
7311
+ onClick={() =>
7312
+ void installRewindAgentConnection(
7313
+ "codex",
7314
+ )
7315
+ }
7316
+ disabled={agentConnectionBusy !== null}
7317
+ >
7318
+ Codex
7319
+ </SettingsActionButton>
7320
+ <SettingsActionButton
7321
+ onClick={() =>
7322
+ void installRewindAgentConnection(
7323
+ "claude-code",
7324
+ )
7325
+ }
7326
+ disabled={agentConnectionBusy !== null}
7327
+ >
7328
+ Claude Code
7329
+ </SettingsActionButton>
7330
+ </>
7331
+ }
7332
+ >
7333
+ {agentConnectionMessage ? (
7334
+ <p
7335
+ className={
7336
+ agentConnectionMessage.kind === "ok"
7337
+ ? "text-xs text-success"
7338
+ : "text-xs text-destructive"
7339
+ }
7340
+ >
7341
+ {agentConnectionMessage.text}
7342
+ </p>
7343
+ ) : null}
7344
+ </SettingsRow>
7345
+ </SettingsGroup>
7346
+
7347
+ <SettingsGroup label="Storage">
7348
+ <SettingsRow
7349
+ label="Search memory"
7350
+ description="Find and replay a recent moment yourself"
7351
+ control={
7352
+ <SettingsActionButton onClick={onOpenMemory}>
7353
+ Search
7354
+ </SettingsActionButton>
7355
+ }
7356
+ />
7357
+ <SettingsRow
7358
+ label="Save last 5 minutes"
7359
+ description="Exports recent memory as video files on this device. Nothing is uploaded."
7360
+ control={
7361
+ <SettingsActionButton
7362
+ onClick={() =>
7363
+ void exportScreenMemoryRecent()
7364
+ }
7365
+ disabled={screenMemoryBusy}
7366
+ >
7367
+ Save
7368
+ </SettingsActionButton>
7369
+ }
7370
+ />
7371
+ <SettingsRow
7372
+ label="On this device"
7373
+ description={`${screenMemorySegments.length} ${screenMemorySegments.length === 1 ? "segment" : "segments"} · ${formatStorageBytes(screenMemoryTotalBytes)}`}
7374
+ control={
7375
+ <>
7376
+ <SettingsActionButton
7377
+ onClick={openScreenMemoryFolder}
7378
+ >
7379
+ Open folder
7380
+ </SettingsActionButton>
7381
+ <SettingsActionButton
7382
+ emphasis="destructive"
7383
+ onClick={() => void clearScreenMemory()}
7384
+ disabled={screenMemoryBusy}
7385
+ >
7386
+ Delete all
7387
+ </SettingsActionButton>
7388
+ </>
7389
+ }
7390
+ />
7391
+ </SettingsGroup>
7392
+ </>
7393
+ ) : null}
7394
+ </>
7395
+ ) : (
7396
+ <SettingsGroup>
7397
+ <div className="p-3 text-sm text-muted-foreground">
7398
+ Turn on Rewind to manage what it remembers.
7399
+ </div>
7400
+ </SettingsGroup>
7401
+ )}
7402
+ </div>
7403
+ </SettingsPopover>
7404
+ <SettingsSwitch
7405
+ checked={rewindOn}
7406
+ onCheckedChange={(next) => {
7407
+ if (next) {
7408
+ setRewindConsentOpen(true);
7409
+ return;
7410
+ }
7411
+ void setScreenMemoryConfig({ enabled: false });
7412
+ }}
7413
+ disabled={screenMemoryConfigBusy || captureControlsLocked}
7414
+ label="Rewind"
7415
+ />
7416
+ </div>
7059
7417
  }
7060
7418
  >
7061
7419
  {screenMemoryMessage ? (
@@ -7074,294 +7432,7 @@ function Setup({
7074
7432
  </p>
7075
7433
  ) : null}
7076
7434
  </SettingsRow>
7077
- {rewindOn ? (
7078
- <>
7079
- <SettingsRow
7080
- label="Remember"
7081
- description="Choose what Rewind captures"
7082
- control={
7083
- <SettingsSelect
7084
- ariaLabel="What Rewind remembers"
7085
- value={screenMemory.captureMode ?? "visuals"}
7086
- onValueChange={(value) => {
7087
- void setScreenMemoryConfig({
7088
- captureMode: value as RewindCaptureMode,
7089
- });
7090
- }}
7091
- disabled={screenMemoryConfigBusy || captureControlsLocked}
7092
- options={[
7093
- { value: "visuals", label: "Screen only" },
7094
- { value: "visuals-audio", label: "Screen + audio" },
7095
- ]}
7096
- />
7097
- }
7098
- />
7099
- <SettingsRow
7100
- label="Time limit"
7101
- description="Choose how long Rewind keeps your screen history"
7102
- control={
7103
- <SettingsSelect
7104
- ariaLabel="Rewind time limit"
7105
- placeholder={`${screenMemory.retentionHours} hours`}
7106
- value={String(screenMemory.retentionHours)}
7107
- onValueChange={(value) => {
7108
- void setScreenMemoryConfig({
7109
- retentionHours: Number(value),
7110
- });
7111
- }}
7112
- disabled={screenMemoryConfigBusy}
7113
- options={[
7114
- { value: "8", label: "8 hours" },
7115
- { value: "24", label: "24 hours" },
7116
- ]}
7117
- />
7118
- }
7119
- />
7120
- <SettingsRow
7121
- label="Storage limit"
7122
- description="Choose how much space Rewind can use on this device"
7123
- control={
7124
- <SettingsSelect
7125
- ariaLabel="Rewind storage limit"
7126
- placeholder={formatStorageBytes(screenMemory.maxBytes)}
7127
- value={String(screenMemory.maxBytes)}
7128
- onValueChange={(value) => {
7129
- void setScreenMemoryConfig({ maxBytes: Number(value) });
7130
- }}
7131
- disabled={screenMemoryConfigBusy}
7132
- options={[
7133
- { value: String(5 * 1024 * 1024 * 1024), label: "5 GB" },
7134
- {
7135
- value: String(20 * 1024 * 1024 * 1024),
7136
- label: "20 GB",
7137
- },
7138
- {
7139
- value: String(50 * 1024 * 1024 * 1024),
7140
- label: "50 GB",
7141
- },
7142
- ]}
7143
- />
7144
- }
7145
- />
7146
- </>
7147
- ) : null}
7148
7435
  </SettingsGroup>
7149
-
7150
- {rewindOn ? (
7151
- <>
7152
- <SettingsGroup label="Privacy">
7153
- <SettingsRow
7154
- label="Excluded apps"
7155
- description={"Apps Rewind never captures"}
7156
- control={
7157
- <SettingsActionButton
7158
- onClick={() => void chooseExcludedApplications()}
7159
- disabled={excludedAppsBusy}
7160
- >
7161
- Choose apps
7162
- </SettingsActionButton>
7163
- }
7164
- >
7165
- {excludedAppGroups.length > 0 ? (
7166
- <div className="grid gap-1">
7167
- {excludedAppGroups.map((app) => (
7168
- <div
7169
- key={app.bundleIds.join(",")}
7170
- className="flex items-center justify-between gap-2"
7171
- >
7172
- <span className="truncate">{app.name}</span>
7173
- <SettingsActionButton
7174
- emphasis="quiet"
7175
- onClick={() =>
7176
- removeExcludedApplications(app.bundleIds)
7177
- }
7178
- >
7179
- Remove
7180
- </SettingsActionButton>
7181
- </div>
7182
- ))}
7183
- </div>
7184
- ) : null}
7185
- </SettingsRow>
7186
- <SettingsRow
7187
- label="Review before sending"
7188
- description="Approve each visual or audio range before an agent receives it"
7189
- control={
7190
- <SettingsSwitch
7191
- checked={screenMemory.reviewBeforeSending !== false}
7192
- onCheckedChange={(next) => {
7193
- void setScreenMemoryConfig({
7194
- reviewBeforeSending: next,
7195
- });
7196
- }}
7197
- disabled={screenMemoryConfigBusy}
7198
- label="Review before sending"
7199
- />
7200
- }
7201
- />
7202
- <SettingsRow
7203
- label="Preview before sending"
7204
- description="Open the range locally so you see exactly what is sent"
7205
- control={
7206
- <SettingsSwitch
7207
- checked={screenMemory.autoPreviewBeforeSending === true}
7208
- onCheckedChange={(next) => {
7209
- void setScreenMemoryConfig({
7210
- autoPreviewBeforeSending: next,
7211
- });
7212
- }}
7213
- disabled={screenMemoryConfigBusy}
7214
- label="Preview before sending"
7215
- />
7216
- }
7217
- />
7218
- <SettingsRow
7219
- label="Keep agent Clips"
7220
- description="Choose how long Clips made for agents stay in your library"
7221
- control={
7222
- <SettingsSelect
7223
- ariaLabel="How long agent-created Clips are kept"
7224
- value={screenMemory.agentClipRetention}
7225
- onValueChange={(value) => {
7226
- void setScreenMemoryConfig({
7227
- agentClipRetention:
7228
- value as ScreenMemoryStatus["config"]["agentClipRetention"],
7229
- });
7230
- }}
7231
- disabled={screenMemoryConfigBusy}
7232
- options={[
7233
- { value: "forever", label: "Forever" },
7234
- { value: "24-hours", label: "24 hours" },
7235
- { value: "7-days", label: "7 days" },
7236
- { value: "30-days", label: "30 days" },
7237
- ]}
7238
- />
7239
- }
7240
- />
7241
- <SettingsRow
7242
- label="Agent activity"
7243
- description="Each time an agent searched this device's memory, newest first"
7244
- >
7245
- {rewindEgressEvents.length === 0 ? (
7246
- <p>No agent has searched it yet.</p>
7247
- ) : (
7248
- <div className="grid gap-1">
7249
- {rewindEgressEvents.slice(0, 10).map((event) => (
7250
- <div
7251
- key={`${event.requestId}-${event.state}`}
7252
- className="flex items-center justify-between gap-2"
7253
- >
7254
- <span className="truncate">
7255
- {new Date(event.occurredAt).toLocaleString()}
7256
- </span>
7257
- <span className="shrink-0">
7258
- {event.state} ·{" "}
7259
- {`${event.evidenceCount} item${event.evidenceCount === 1 ? "" : "s"}`}
7260
- </span>
7261
- </div>
7262
- ))}
7263
- </div>
7264
- )}
7265
- </SettingsRow>
7266
- </SettingsGroup>
7267
-
7268
- <SettingsGroup label="Agents">
7269
- <SettingsRow
7270
- label="Setup prompt"
7271
- description="Paste it into an agent once to install Rewind's instructions"
7272
- control={
7273
- <>
7274
- <SettingsActionButton
7275
- emphasis="quiet"
7276
- onClick={onOpenRewindDocs}
7277
- >
7278
- Learn more
7279
- </SettingsActionButton>
7280
- <SettingsActionButton onClick={onCopyRewindAgentPrompt}>
7281
- {rewindAgentPromptCopied ? "Copied" : "Copy"}
7282
- </SettingsActionButton>
7283
- </>
7284
- }
7285
- />
7286
- <SettingsRow
7287
- label="Connect an agent"
7288
- description="Gives a local agent access to this device's Rewind memory"
7289
- control={
7290
- <>
7291
- <SettingsActionButton
7292
- onClick={() => void installRewindAgentConnection("codex")}
7293
- disabled={agentConnectionBusy !== null}
7294
- >
7295
- Codex
7296
- </SettingsActionButton>
7297
- <SettingsActionButton
7298
- onClick={() =>
7299
- void installRewindAgentConnection("claude-code")
7300
- }
7301
- disabled={agentConnectionBusy !== null}
7302
- >
7303
- Claude Code
7304
- </SettingsActionButton>
7305
- </>
7306
- }
7307
- >
7308
- {agentConnectionMessage ? (
7309
- <p
7310
- className={
7311
- agentConnectionMessage.kind === "ok"
7312
- ? "text-xs text-success"
7313
- : "text-xs text-destructive"
7314
- }
7315
- >
7316
- {agentConnectionMessage.text}
7317
- </p>
7318
- ) : null}
7319
- </SettingsRow>
7320
- </SettingsGroup>
7321
-
7322
- <SettingsGroup label="Storage">
7323
- <SettingsRow
7324
- label="Search memory"
7325
- description="Find and replay a recent moment yourself"
7326
- control={
7327
- <SettingsActionButton onClick={onOpenMemory}>
7328
- Search
7329
- </SettingsActionButton>
7330
- }
7331
- />
7332
- <SettingsRow
7333
- label="Save last 5 minutes"
7334
- description="Exports recent memory as video files on this device. Nothing is uploaded."
7335
- control={
7336
- <SettingsActionButton
7337
- onClick={() => void exportScreenMemoryRecent()}
7338
- disabled={screenMemoryBusy}
7339
- >
7340
- Save
7341
- </SettingsActionButton>
7342
- }
7343
- />
7344
- <SettingsRow
7345
- label="On this device"
7346
- description={`${screenMemorySegments.length} ${screenMemorySegments.length === 1 ? "segment" : "segments"} · ${formatStorageBytes(screenMemoryTotalBytes)}`}
7347
- control={
7348
- <>
7349
- <SettingsActionButton onClick={openScreenMemoryFolder}>
7350
- Open folder
7351
- </SettingsActionButton>
7352
- <SettingsActionButton
7353
- emphasis="destructive"
7354
- onClick={() => void clearScreenMemory()}
7355
- disabled={screenMemoryBusy}
7356
- >
7357
- Delete all
7358
- </SettingsActionButton>
7359
- </>
7360
- }
7361
- />
7362
- </SettingsGroup>
7363
- </>
7364
- ) : null}
7365
7436
  </div>
7366
7437
  );
7367
7438
  }