@agent-native/core 0.114.8 → 0.114.10
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 +2 -2
- package/corpus/core/CHANGELOG.md +21 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +31 -3
- package/corpus/core/src/agent/engine/anthropic-engine.ts +20 -3
- package/corpus/core/src/agent/engine/builder-engine.ts +56 -9
- package/corpus/core/src/agent/engine/first-event-timeout.ts +64 -0
- package/corpus/core/src/client/AssistantChat.tsx +7 -3
- package/corpus/core/src/client/resources/ResourcesPanel.tsx +1 -1
- package/corpus/core/src/integrations/computer-supervision-store.ts +4 -4
- package/corpus/core/src/integrations/pending-tasks-store.ts +1 -1
- package/corpus/core/src/integrations/remote-commands-store.ts +5 -5
- package/corpus/core/src/integrations/remote-devices-store.ts +3 -3
- package/corpus/core/src/integrations/remote-push-store.ts +3 -3
- package/corpus/core/src/observability/traces.ts +38 -1
- package/corpus/core/src/server/action-routes.ts +23 -1
- package/corpus/core/src/server/http-response-telemetry.ts +6 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/corpus/templates/analytics/actions/account-deep-dive.ts +41 -6
- package/corpus/templates/analytics/changelog/2026-07-20-account-deep-dives-finish-with-partial-data-when-hubspot-lo.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-20-dashboard-email-reports-now-include-an-image-when-a-single-p.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +11 -3
- package/corpus/templates/calendar/actions/create-event.ts +5 -0
- package/corpus/templates/calendar/app/components/ThemeToggle.tsx +4 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +0 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +309 -421
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +349 -0
- package/corpus/templates/calendar/app/i18n-data.ts +110 -0
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +6 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +3 -0
- package/corpus/templates/calendar/app/root.tsx +4 -2
- package/corpus/templates/calendar/changelog/2026-07-20-events-can-be-updated-inline-with-date-time-timezone-and-rep.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-20-public-booking-pages-no-longer-hydrate-with-a-theme-mismatch.md +6 -0
- package/corpus/templates/calendar/shared/api.ts +1 -0
- package/corpus/templates/clips/AGENTS.md +7 -0
- package/corpus/templates/clips/actions/create-recording.ts +51 -10
- package/corpus/templates/clips/actions/get-recording-player-data.ts +34 -5
- package/corpus/templates/clips/actions/lib/transcript-preview.ts +48 -0
- package/corpus/templates/clips/actions/list-recordings.ts +15 -4
- package/corpus/templates/clips/actions/regenerate-summary.ts +32 -0
- package/corpus/templates/clips/actions/search-recordings.ts +28 -6
- package/corpus/templates/clips/actions/view-screen.ts +54 -47
- package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -5
- package/corpus/templates/clips/app/components/library/recording-card.tsx +29 -32
- package/corpus/templates/clips/app/components/meetings/quick-ask-sidebar.tsx +13 -0
- package/corpus/templates/clips/changelog/2026-07-20-agent-summaries-use-bounded-transcript-context.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-clip-summaries-now-finish-without-sending-a-long-transcript-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-clips-agents-only-discover-recordings-you-own-or-have-alread.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-hosted-recordings-no-longer-start-with-an-impossible-buffered-upload.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-meeting-recordings-now-offer-a-cleaner-transcript-view-with-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-shared-clips-can-be-shared-with-limited-access-and-cards-sho.md +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +12 -8
- package/corpus/templates/clips/desktop/src/overlays/pill-logo.tsx +2 -15
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +107 -340
- package/corpus/templates/clips/desktop/src/styles.css +27 -145
- package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +6 -7
- package/corpus/templates/clips/server/lib/agent-recording-access.ts +73 -0
- package/corpus/templates/clips/server/plugins/agent-chat.ts +5 -2
- package/corpus/templates/plan/app/lib/plan-local-bridge.ts +18 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +10 -11
- package/corpus/templates/plan/changelog/2026-07-20-bridged-local-plans-now-load-comments-from-the-local-workspace.md +6 -0
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +27 -4
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/anthropic-engine.js +19 -4
- package/dist/agent/engine/anthropic-engine.js.map +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +40 -7
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/engine/first-event-timeout.d.ts +25 -0
- package/dist/agent/engine/first-event-timeout.d.ts.map +1 -0
- package/dist/agent/engine/first-event-timeout.js +49 -0
- package/dist/agent/engine/first-event-timeout.js.map +1 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +7 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +1 -1
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/computer-supervision-store.js +4 -4
- package/dist/integrations/computer-supervision-store.js.map +1 -1
- package/dist/integrations/pending-tasks-store.js +1 -1
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/remote-commands-store.js +5 -5
- package/dist/integrations/remote-commands-store.js.map +1 -1
- package/dist/integrations/remote-devices-store.js +3 -3
- package/dist/integrations/remote-devices-store.js.map +1 -1
- package/dist/integrations/remote-push-store.js +3 -3
- package/dist/integrations/remote-push-store.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +23 -1
- package/dist/observability/traces.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +23 -1
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/http-response-telemetry.d.ts +2 -0
- package/dist/server/http-response-telemetry.d.ts.map +1 -1
- package/dist/server/http-response-telemetry.js +5 -1
- package/dist/server/http-response-telemetry.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/package.json +2 -2
- package/src/agent/engine/ai-sdk-engine.ts +31 -3
- package/src/agent/engine/anthropic-engine.ts +20 -3
- package/src/agent/engine/builder-engine.ts +56 -9
- package/src/agent/engine/first-event-timeout.ts +64 -0
- package/src/client/AssistantChat.tsx +7 -3
- package/src/client/resources/ResourcesPanel.tsx +1 -1
- package/src/integrations/computer-supervision-store.ts +4 -4
- package/src/integrations/pending-tasks-store.ts +1 -1
- package/src/integrations/remote-commands-store.ts +5 -5
- package/src/integrations/remote-devices-store.ts +3 -3
- package/src/integrations/remote-push-store.ts +3 -3
- package/src/observability/traces.ts +38 -1
- package/src/server/action-routes.ts +23 -1
- package/src/server/http-response-telemetry.ts +6 -1
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
|
@@ -5321,16 +5321,32 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5321
5321
|
background: rgba(26, 26, 26, 0.95);
|
|
5322
5322
|
color: white;
|
|
5323
5323
|
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1);
|
|
5324
|
+
cursor: grab;
|
|
5324
5325
|
backdrop-filter: blur(12px);
|
|
5325
5326
|
-webkit-backdrop-filter: blur(12px);
|
|
5326
5327
|
}
|
|
5327
5328
|
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5329
|
+
.pill-inner:active {
|
|
5330
|
+
cursor: grabbing;
|
|
5331
|
+
}
|
|
5332
|
+
|
|
5333
|
+
.pill-inner:not(.pill-inner-compact) {
|
|
5334
|
+
animation: pill-transcript-in 180ms ease-out;
|
|
5335
|
+
}
|
|
5336
|
+
|
|
5337
|
+
@keyframes pill-transcript-in {
|
|
5338
|
+
from {
|
|
5339
|
+
opacity: 0;
|
|
5340
|
+
transform: translateY(4px) scale(0.985);
|
|
5341
|
+
}
|
|
5342
|
+
to {
|
|
5343
|
+
opacity: 1;
|
|
5344
|
+
transform: translateY(0) scale(1);
|
|
5345
|
+
}
|
|
5346
|
+
}
|
|
5347
|
+
|
|
5348
|
+
/* Collapsed (not expanded). Shrinks to logo + waveform; controls revealed via
|
|
5349
|
+
CSS :hover which works at the WebKit level even when the app is unfocused. */
|
|
5334
5350
|
.pill-inner-compact {
|
|
5335
5351
|
width: fit-content;
|
|
5336
5352
|
height: fit-content;
|
|
@@ -5393,9 +5409,7 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5393
5409
|
height: 12px;
|
|
5394
5410
|
}
|
|
5395
5411
|
|
|
5396
|
-
/* Collapsed = vertical capsule:
|
|
5397
|
-
pill clickable to expand. Detached mode keeps its own horizontal layout, so
|
|
5398
|
-
this only applies to the non-detached collapsed pill. */
|
|
5412
|
+
/* Collapsed = vertical capsule: logo on top, waveform below. */
|
|
5399
5413
|
.pill-inner-compact .pill-header.pill-vertical {
|
|
5400
5414
|
flex-direction: column;
|
|
5401
5415
|
gap: 0;
|
|
@@ -5416,20 +5430,11 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5416
5430
|
}
|
|
5417
5431
|
|
|
5418
5432
|
.pill-vertical .pill-wave-canvas {
|
|
5419
|
-
height:
|
|
5420
|
-
width:
|
|
5421
|
-
}
|
|
5422
|
-
|
|
5423
|
-
/* Dual-stream (mic + system audio) stacked in the collapsed capsule. */
|
|
5424
|
-
.pill-vertical .pill-wave-dual {
|
|
5425
|
-
height: 26px;
|
|
5426
|
-
width: 18px;
|
|
5427
|
-
gap: 1px;
|
|
5433
|
+
height: 22px;
|
|
5434
|
+
width: 22px;
|
|
5428
5435
|
}
|
|
5429
5436
|
|
|
5430
|
-
/* Wrapper around logo + waveform.
|
|
5431
|
-
detached); a real, clickable box only in the collapsed vertical capsule —
|
|
5432
|
-
clicking it expands the pill, and hovering tints it. */
|
|
5437
|
+
/* Wrapper around logo + waveform. */
|
|
5433
5438
|
.pill-media {
|
|
5434
5439
|
display: contents;
|
|
5435
5440
|
}
|
|
@@ -5440,9 +5445,7 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5440
5445
|
align-items: center;
|
|
5441
5446
|
align-self: stretch;
|
|
5442
5447
|
gap: 5px;
|
|
5443
|
-
/*
|
|
5444
|
-
upper region (to the top + side edges of the capsule) while leaving the
|
|
5445
|
-
logo/waveform in their original positions. */
|
|
5448
|
+
/* The compact media area is the click-to-expand target. */
|
|
5446
5449
|
padding: 11px 7px 4px;
|
|
5447
5450
|
margin: -11px -7px 0;
|
|
5448
5451
|
border-radius: 18px 18px 0 0;
|
|
@@ -5455,62 +5458,12 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5455
5458
|
background: rgba(255, 255, 255, 0.08);
|
|
5456
5459
|
}
|
|
5457
5460
|
|
|
5458
|
-
/* Drag grip revealed at the bottom of the collapsed capsule on hover. The
|
|
5459
|
-
pill grows downward to fit it (window is sized with headroom for this).
|
|
5460
|
-
Dragging the pill is wired on the whole window; this is the visible
|
|
5461
|
-
affordance and gets its own hover tint. */
|
|
5462
|
-
.pill-vertical .pill-vgrip {
|
|
5463
|
-
display: flex;
|
|
5464
|
-
align-items: center;
|
|
5465
|
-
justify-content: center;
|
|
5466
|
-
height: 0;
|
|
5467
|
-
margin-top: 0;
|
|
5468
|
-
overflow: hidden;
|
|
5469
|
-
opacity: 0;
|
|
5470
|
-
border-radius: 8px;
|
|
5471
|
-
color: rgba(255, 255, 255, 0.45);
|
|
5472
|
-
cursor: grab;
|
|
5473
|
-
transition:
|
|
5474
|
-
height 140ms ease,
|
|
5475
|
-
margin-top 140ms ease,
|
|
5476
|
-
background 120ms ease,
|
|
5477
|
-
opacity 120ms ease;
|
|
5478
|
-
}
|
|
5479
|
-
|
|
5480
|
-
.pill-inner-compact:hover .pill-vertical .pill-vgrip,
|
|
5481
|
-
.pill-inner-compact.pill-hovered .pill-vertical .pill-vgrip {
|
|
5482
|
-
height: 14px;
|
|
5483
|
-
margin-top: 3px;
|
|
5484
|
-
opacity: 1;
|
|
5485
|
-
}
|
|
5486
|
-
|
|
5487
|
-
.pill-vertical .pill-vgrip:active {
|
|
5488
|
-
cursor: grabbing;
|
|
5489
|
-
}
|
|
5490
|
-
|
|
5491
5461
|
.pill-timer {
|
|
5492
5462
|
font-size: 13px;
|
|
5493
5463
|
font-weight: 500;
|
|
5494
5464
|
font-variant-numeric: tabular-nums;
|
|
5495
5465
|
}
|
|
5496
5466
|
|
|
5497
|
-
.pill-wave-dual {
|
|
5498
|
-
display: flex;
|
|
5499
|
-
height: 26px;
|
|
5500
|
-
width: 48px;
|
|
5501
|
-
flex-shrink: 0;
|
|
5502
|
-
flex-direction: column;
|
|
5503
|
-
align-items: stretch;
|
|
5504
|
-
gap: 2px;
|
|
5505
|
-
}
|
|
5506
|
-
|
|
5507
|
-
.pill-wave-canvas-half {
|
|
5508
|
-
width: 100%;
|
|
5509
|
-
flex: 1;
|
|
5510
|
-
min-height: 0;
|
|
5511
|
-
display: block;
|
|
5512
|
-
}
|
|
5513
|
-
|
|
5514
5467
|
.pill-wave-canvas {
|
|
5515
5468
|
height: 26px;
|
|
5516
5469
|
width: 48px;
|
|
@@ -5564,24 +5517,6 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5564
5517
|
background: rgba(255, 255, 255, 0.1);
|
|
5565
5518
|
}
|
|
5566
5519
|
|
|
5567
|
-
.pill-drag-handle {
|
|
5568
|
-
position: absolute;
|
|
5569
|
-
bottom: 4px;
|
|
5570
|
-
left: 50%;
|
|
5571
|
-
transform: translateX(-50%);
|
|
5572
|
-
height: 4px;
|
|
5573
|
-
width: 40px;
|
|
5574
|
-
cursor: pointer;
|
|
5575
|
-
border-radius: 999px;
|
|
5576
|
-
background: rgba(255, 255, 255, 0.3);
|
|
5577
|
-
border: none;
|
|
5578
|
-
transition: background 120ms;
|
|
5579
|
-
}
|
|
5580
|
-
|
|
5581
|
-
.pill-drag-handle:hover {
|
|
5582
|
-
background: rgba(255, 255, 255, 0.5);
|
|
5583
|
-
}
|
|
5584
|
-
|
|
5585
5520
|
/* Whisper model status row in Settings */
|
|
5586
5521
|
|
|
5587
5522
|
.whisper-status {
|
|
@@ -5707,29 +5642,6 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5707
5642
|
overflow: hidden;
|
|
5708
5643
|
}
|
|
5709
5644
|
|
|
5710
|
-
/* Meeting mode: transcript + notes side by side. */
|
|
5711
|
-
.pill-split {
|
|
5712
|
-
display: flex;
|
|
5713
|
-
flex: 1;
|
|
5714
|
-
min-height: 0;
|
|
5715
|
-
overflow: hidden;
|
|
5716
|
-
}
|
|
5717
|
-
|
|
5718
|
-
.pill-split-pane {
|
|
5719
|
-
display: flex;
|
|
5720
|
-
flex-direction: column;
|
|
5721
|
-
flex: 1 1 50%;
|
|
5722
|
-
min-width: 0;
|
|
5723
|
-
min-height: 0;
|
|
5724
|
-
}
|
|
5725
|
-
|
|
5726
|
-
.pill-split-divider {
|
|
5727
|
-
width: 1px;
|
|
5728
|
-
flex-shrink: 0;
|
|
5729
|
-
margin: 8px 0;
|
|
5730
|
-
background: rgba(255, 255, 255, 0.1);
|
|
5731
|
-
}
|
|
5732
|
-
|
|
5733
5645
|
.pill-pane-label {
|
|
5734
5646
|
flex-shrink: 0;
|
|
5735
5647
|
padding: 8px 12px 2px;
|
|
@@ -5772,36 +5684,6 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5772
5684
|
cursor: default;
|
|
5773
5685
|
}
|
|
5774
5686
|
|
|
5775
|
-
/* In the split, the transcript area should fill its pane below the label. */
|
|
5776
|
-
.pill-split-pane .pill-transcript-area {
|
|
5777
|
-
flex: 1;
|
|
5778
|
-
min-height: 0;
|
|
5779
|
-
}
|
|
5780
|
-
|
|
5781
|
-
.pill-notes-area {
|
|
5782
|
-
min-height: 0;
|
|
5783
|
-
flex: 1;
|
|
5784
|
-
display: flex;
|
|
5785
|
-
overflow: hidden;
|
|
5786
|
-
padding: 4px 12px 8px;
|
|
5787
|
-
}
|
|
5788
|
-
|
|
5789
|
-
.pill-notes-textarea {
|
|
5790
|
-
flex: 1;
|
|
5791
|
-
background: transparent;
|
|
5792
|
-
border: none;
|
|
5793
|
-
outline: none;
|
|
5794
|
-
resize: none;
|
|
5795
|
-
color: #f5f5f5;
|
|
5796
|
-
font-size: 12px;
|
|
5797
|
-
line-height: 1.5;
|
|
5798
|
-
font-family: inherit;
|
|
5799
|
-
}
|
|
5800
|
-
|
|
5801
|
-
.pill-notes-textarea::placeholder {
|
|
5802
|
-
color: #71717a;
|
|
5803
|
-
}
|
|
5804
|
-
|
|
5805
5687
|
.pill-saved-bar {
|
|
5806
5688
|
flex-shrink: 0;
|
|
5807
5689
|
display: flex;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
//!
|
|
10
10
|
//! Two visual modes (driven entirely from the React side via the URL hash):
|
|
11
11
|
//!
|
|
12
|
-
//! - `meeting` — meeting-aware pill with
|
|
12
|
+
//! - `meeting` — meeting-aware pill with a combined audio meter.
|
|
13
13
|
//! - `clip` — solid-mic pill for non-meeting recording sessions.
|
|
14
14
|
//!
|
|
15
15
|
//! The pill is used by meeting-aware recordings and Wispr-style voice
|
|
@@ -68,9 +68,9 @@ static PILL_HOVER_TRACKING: AtomicBool = AtomicBool::new(false);
|
|
|
68
68
|
/// expanded form stretches horizontally to fit the live-transcript area.
|
|
69
69
|
const PILL_W_LOGICAL: u32 = 38;
|
|
70
70
|
const PILL_W_EXPANDED_LOGICAL: u32 = 480;
|
|
71
|
-
/// Meeting mode
|
|
72
|
-
///
|
|
73
|
-
const PILL_W_EXPANDED_MEETING_LOGICAL: u32 =
|
|
71
|
+
/// Meeting mode uses the same focused transcript width as other recordings;
|
|
72
|
+
/// live notes are intentionally kept out of this compact overlay.
|
|
73
|
+
const PILL_W_EXPANDED_MEETING_LOGICAL: u32 = 480;
|
|
74
74
|
const PILL_H_LOGICAL: u32 = 92;
|
|
75
75
|
const PILL_H_EXPANDED_LOGICAL: u32 = 340;
|
|
76
76
|
/// Bottom margin from the screen edge, logical px. Granola uses ~24.
|
|
@@ -240,9 +240,8 @@ fn pill_content_size_physical(app: &AppHandle, expanded: bool) -> (u32, u32) {
|
|
|
240
240
|
let (w_log, h_log) = if detached {
|
|
241
241
|
(PILL_DETACHED_W_LOGICAL, PILL_DETACHED_H_LOGICAL)
|
|
242
242
|
} else if expanded {
|
|
243
|
-
// Meeting mode (right-side anchor)
|
|
244
|
-
//
|
|
245
|
-
// transcript-only width.
|
|
243
|
+
// Meeting mode (right-side anchor) keeps a focused transcript-only
|
|
244
|
+
// panel; plain clip recordings use the same width today.
|
|
246
245
|
let w = if PILL_RIGHT_SIDE.load(Ordering::Relaxed) {
|
|
247
246
|
PILL_W_EXPANDED_MEETING_LOGICAL
|
|
248
247
|
} else {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
2
|
+
import { accessFilter } from "@agent-native/core/sharing";
|
|
3
|
+
import { and, eq, or, sql, type SQL } from "drizzle-orm";
|
|
4
|
+
|
|
5
|
+
type AgentRecordingAccessOptions = {
|
|
6
|
+
agentOnly?: boolean;
|
|
7
|
+
userEmail?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function isAgentRecordingCaller(caller: string | undefined): boolean {
|
|
11
|
+
return caller === "tool" || caller === "mcp" || caller === "a2a";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeEmail(email: string | undefined): string | null {
|
|
15
|
+
const normalized = email?.trim().toLowerCase();
|
|
16
|
+
return normalized || null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Scope agent recording reads to normal access plus public recordings the
|
|
21
|
+
* current signed-in user has already opened. Public visibility alone never
|
|
22
|
+
* makes a recording discoverable, but explicit user and organization shares
|
|
23
|
+
* remain discoverable.
|
|
24
|
+
*/
|
|
25
|
+
export function agentRecordingAccessFilter(
|
|
26
|
+
resourceTable: any,
|
|
27
|
+
sharesTable: any,
|
|
28
|
+
viewersTable: any,
|
|
29
|
+
options: AgentRecordingAccessOptions = {},
|
|
30
|
+
): SQL {
|
|
31
|
+
const normalAccess = accessFilter(resourceTable, sharesTable);
|
|
32
|
+
if (!options.agentOnly) return normalAccess;
|
|
33
|
+
|
|
34
|
+
const normalizedEmail = normalizeEmail(
|
|
35
|
+
options.userEmail ?? getRequestUserEmail(),
|
|
36
|
+
);
|
|
37
|
+
if (!normalizedEmail) return sql`1 = 0`;
|
|
38
|
+
|
|
39
|
+
const viewed = sql`exists (select 1 from ${viewersTable}
|
|
40
|
+
where ${viewersTable.recordingId} = ${resourceTable.id}
|
|
41
|
+
and lower(${viewersTable.viewerEmail}) = ${normalizedEmail})`;
|
|
42
|
+
const owner = sql`lower(${resourceTable.ownerEmail}) = ${normalizedEmail}`;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
or(
|
|
46
|
+
owner,
|
|
47
|
+
normalAccess,
|
|
48
|
+
and(eq(resourceTable.visibility, "public"), viewed),
|
|
49
|
+
) ?? normalAccess
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function hasViewedPublicRecording(
|
|
54
|
+
db: any,
|
|
55
|
+
viewersTable: any,
|
|
56
|
+
recordingId: string,
|
|
57
|
+
userEmail = getRequestUserEmail(),
|
|
58
|
+
): Promise<boolean> {
|
|
59
|
+
const normalizedEmail = normalizeEmail(userEmail);
|
|
60
|
+
if (!normalizedEmail) return false;
|
|
61
|
+
|
|
62
|
+
const [viewer] = await db
|
|
63
|
+
.select({ id: viewersTable.id })
|
|
64
|
+
.from(viewersTable)
|
|
65
|
+
.where(
|
|
66
|
+
and(
|
|
67
|
+
eq(viewersTable.recordingId, recordingId),
|
|
68
|
+
sql`lower(${viewersTable.viewerEmail}) = ${normalizedEmail}`,
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
.limit(1);
|
|
72
|
+
return Boolean(viewer);
|
|
73
|
+
}
|
|
@@ -42,7 +42,10 @@ export default createAgentChatPlugin({
|
|
|
42
42
|
initialToolNames: INITIAL_TOOL_NAMES,
|
|
43
43
|
extraContext: async () =>
|
|
44
44
|
`<clips-transcript-guidance>
|
|
45
|
-
The transcript in view-screen
|
|
46
|
-
</clips-transcript-guidance
|
|
45
|
+
The transcript in view-screen and get-recording-player-data are bounded previews when called by the agent. When previewTruncated is true, the text is expected to end mid-sentence and is never evidence that transcription stopped early. Use the bounded payload for a concise summary and do not request or reconstruct the omitted transcript by searching other recordings. For a failed or pending transcript, use request-transcript with force=true. For an explicit fresh retry of an existing ready transcript, also pass regenerate=true. Agent-triggered retries are queued for the durable worker and return pending while processing.
|
|
46
|
+
</clips-transcript-guidance>
|
|
47
|
+
<clips-recording-discovery>
|
|
48
|
+
Clips public recordings are unlisted-by-link, not a searchable public catalog. Treat a recording as known only when it is owned by the current user, already appears in the current screen, or the current user has already viewed it. Do not use list-recordings or search-recordings to discover someone else's clips, recover from a failed direct lookup, answer a date/time question, or search by a title that was not supplied by the user. A question such as when this clip was created refers to the clip already in context; answer it from current-screen or get-recording-player-data. If a direct recording lookup fails, report the failure and stop instead of broadening the search. Only inspect another clip when the user provides its share URL or recording id in the request.
|
|
49
|
+
</clips-recording-discovery>`,
|
|
47
50
|
resolveOrgId: async (event) => (await getOrgContext(event)).orgId,
|
|
48
51
|
});
|
|
@@ -480,6 +480,24 @@ export async function fetchLocalPlanBridgeBundle(
|
|
|
480
480
|
return localPlanBridgePayloadToBundle(payload, fallbackSlug);
|
|
481
481
|
}
|
|
482
482
|
|
|
483
|
+
export async function fetchLocalPlanBridgeComments(
|
|
484
|
+
bridgeUrl: string,
|
|
485
|
+
): Promise<LocalPlanBundle["comments"]> {
|
|
486
|
+
const response = await fetch(localPlanBridgeCommentsUrl(bridgeUrl), {
|
|
487
|
+
cache: "no-store",
|
|
488
|
+
});
|
|
489
|
+
const payload = (await response
|
|
490
|
+
.json()
|
|
491
|
+
.catch(() => null)) as LocalPlanBridgePayload | null;
|
|
492
|
+
if (!response.ok || !payload?.ok) {
|
|
493
|
+
throw new Error(
|
|
494
|
+
payload?.error ||
|
|
495
|
+
`Local plan bridge returned ${response.status || "an error"}.`,
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
return (payload.comments ?? []).filter((comment) => !comment.deletedAt);
|
|
499
|
+
}
|
|
500
|
+
|
|
483
501
|
export async function updateLocalPlanBridgeComments(
|
|
484
502
|
bridgeUrl: string,
|
|
485
503
|
fallbackSlug: string,
|
|
@@ -270,6 +270,7 @@ import {
|
|
|
270
270
|
} from "@/lib/plan-comment-editor-helpers";
|
|
271
271
|
import { planDocumentTitle } from "@/lib/plan-document-title";
|
|
272
272
|
import {
|
|
273
|
+
fetchLocalPlanBridgeComments,
|
|
273
274
|
fetchLocalPlanBridgeBundle,
|
|
274
275
|
localNetworkAccessPermissionState,
|
|
275
276
|
localPlanBridgeUrlFromLocation,
|
|
@@ -2082,26 +2083,24 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
|
|
|
2082
2083
|
);
|
|
2083
2084
|
// Bridge bundles carry no comments; load comments.json from the colocated
|
|
2084
2085
|
// folder so they render and survive refresh in bridge mode too.
|
|
2085
|
-
const localPlanBridgeCommentsQuery =
|
|
2086
|
-
"
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
},
|
|
2093
|
-
);
|
|
2086
|
+
const localPlanBridgeCommentsQuery = useQuery<LocalPlanBundle["comments"]>({
|
|
2087
|
+
queryKey: ["local-plan-bridge-comments", localPlanBridgeUrl],
|
|
2088
|
+
enabled: localPlanMode && Boolean(localPlanSlug && localPlanBridgeUrl),
|
|
2089
|
+
refetchInterval: false,
|
|
2090
|
+
retry: false,
|
|
2091
|
+
queryFn: () => fetchLocalPlanBridgeComments(localPlanBridgeUrl ?? ""),
|
|
2092
|
+
});
|
|
2094
2093
|
const localPlanData = useMemo(
|
|
2095
2094
|
() =>
|
|
2096
2095
|
localPlanBridgeUrl
|
|
2097
2096
|
? mergeLocalBridgeComments(
|
|
2098
2097
|
localPlanBridgeQuery.data,
|
|
2099
|
-
localPlanBridgeCommentsQuery.data
|
|
2098
|
+
localPlanBridgeCommentsQuery.data,
|
|
2100
2099
|
)
|
|
2101
2100
|
: localPlanQuery.data,
|
|
2102
2101
|
[
|
|
2103
2102
|
localPlanBridgeQuery.data,
|
|
2104
|
-
localPlanBridgeCommentsQuery.data
|
|
2103
|
+
localPlanBridgeCommentsQuery.data,
|
|
2105
2104
|
localPlanBridgeUrl,
|
|
2106
2105
|
localPlanQuery.data,
|
|
2107
2106
|
],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-sdk-engine.d.ts","sourceRoot":"","sources":["../../../src/agent/engine/ai-sdk-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAYH,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"ai-sdk-engine.d.ts","sourceRoot":"","sources":["../../../src/agent/engine/ai-sdk-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAYH,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAe7E,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAInB,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMxD,QAAA,MAAM,qBAAqB,EAAE,MAAM,CAAC,aAAa,EAAE,kBAAkB,CAyDpE,CAAC;AAMF,QAAA,MAAM,uBAAuB,EAKxB,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;AAEnC,QAAA,MAAM,yBAAyB,EAKf,MAAM,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;AAEzD,QAAA,MAAM,iBAAiB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,EAAE,CAStD,CAAC;AAEF,QAAA,MAAM,iBAAiB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CASpD,CAAC;AA4CF,kDAAkD;AAClD,MAAM,WAAW,iBAAiB;IAChC,sFAAsF;IACtF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0GAA0G;IAC1G,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,yFAAyF;IACzF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAuUD,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,aAAa,EACvB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,WAAW,CAEb;AA8CD,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,yBAAyB,EACzB,iBAAiB,EACjB,iBAAiB,GAClB,CAAC"}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { clearProviderCredentialAuthFailure, readDeployCredentialEnv, recordProviderCredentialAuthFailure, } from "../../server/credential-provider.js";
|
|
15
15
|
import { anthropicManualThinkingBudget, normalizeReasoningEffortForModel, supportsClaudeAdaptiveThinking, } from "../../shared/reasoning-effort.js";
|
|
16
16
|
import { AI_SDK_MODEL_CONFIG } from "../model-config.js";
|
|
17
|
+
import { createFirstEventAbortController, FIRST_STREAM_EVENT_TIMEOUT_MS, } from "./first-event-timeout.js";
|
|
17
18
|
import { clampThinkingBudgetTokens, resolveMaxOutputTokensForEngine, } from "./output-tokens.js";
|
|
18
19
|
import { engineToolsToAISDK, engineMessagesToAISDK, aiSdkPartToEngineEvents, aiSdkStepToAssistantContent, } from "./translate-ai-sdk.js";
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
@@ -299,6 +300,7 @@ class AISDKEngine {
|
|
|
299
300
|
}
|
|
300
301
|
}
|
|
301
302
|
let assistantContent = [];
|
|
303
|
+
const firstEventAbort = createFirstEventAbortController(opts.abortSignal);
|
|
302
304
|
try {
|
|
303
305
|
const result = streamText({
|
|
304
306
|
model: providerModel,
|
|
@@ -309,7 +311,7 @@ class AISDKEngine {
|
|
|
309
311
|
...(opts.temperature !== undefined
|
|
310
312
|
? { temperature: opts.temperature }
|
|
311
313
|
: {}),
|
|
312
|
-
abortSignal:
|
|
314
|
+
abortSignal: firstEventAbort.signal,
|
|
313
315
|
onStepFinish: (step) => {
|
|
314
316
|
assistantContent = aiSdkStepToAssistantContent(step);
|
|
315
317
|
},
|
|
@@ -320,7 +322,17 @@ class AISDKEngine {
|
|
|
320
322
|
// Buffer the terminal stop so assistant-content can be emitted just
|
|
321
323
|
// before it, regardless of where `finish` arrives in the stream.
|
|
322
324
|
let bufferedStop;
|
|
325
|
+
let sawFirstEvent = false;
|
|
323
326
|
for await (const part of result.fullStream) {
|
|
327
|
+
// "start" is a synthetic lifecycle marker the AI SDK enqueues
|
|
328
|
+
// synchronously when the stream begins — before any provider bytes
|
|
329
|
+
// arrive — so it does not count as real progress. Every other part
|
|
330
|
+
// (including "start-step", only enqueued on the step's first real
|
|
331
|
+
// chunk) proves the provider is actually responding.
|
|
332
|
+
if (!sawFirstEvent && part?.type !== "start") {
|
|
333
|
+
sawFirstEvent = true;
|
|
334
|
+
firstEventAbort.markFirstEvent();
|
|
335
|
+
}
|
|
324
336
|
for (const event of aiSdkPartToEngineEvents(part)) {
|
|
325
337
|
if (event.type === "stop") {
|
|
326
338
|
bufferedStop = event;
|
|
@@ -330,6 +342,12 @@ class AISDKEngine {
|
|
|
330
342
|
}
|
|
331
343
|
}
|
|
332
344
|
}
|
|
345
|
+
// AI SDK surfaces an aborted stream as a graceful `{type: "abort"}`
|
|
346
|
+
// part rather than a thrown error, so a first-event timeout would
|
|
347
|
+
// otherwise fall through to the normal end_turn completion below.
|
|
348
|
+
if (!sawFirstEvent && firstEventAbort.didTimeout()) {
|
|
349
|
+
throw new Error(`Model request produced no stream events within ${FIRST_STREAM_EVENT_TIMEOUT_MS / 1000}s; the connection appears wedged.`);
|
|
350
|
+
}
|
|
333
351
|
yield { type: "assistant-content", parts: assistantContent };
|
|
334
352
|
await clearProviderCredentialAuthFailure({
|
|
335
353
|
key: PROVIDER_ENV_VARS[this.provider][0],
|
|
@@ -338,16 +356,18 @@ class AISDKEngine {
|
|
|
338
356
|
yield bufferedStop ?? { type: "stop", reason: "end_turn" };
|
|
339
357
|
}
|
|
340
358
|
catch (err) {
|
|
359
|
+
const timedOut = firstEventAbort.didTimeout();
|
|
341
360
|
// Surface structured fields from AI SDK's APICallError so
|
|
342
361
|
// isRetryableError can check statusCode/providerRetryable directly
|
|
343
362
|
// rather than keyword-matching the message string.
|
|
344
363
|
const statusCode = typeof err?.statusCode === "number" ? err.statusCode : undefined;
|
|
345
364
|
const errorMessage = err?.message ?? String(err);
|
|
346
|
-
const isConnectionError =
|
|
365
|
+
const isConnectionError = !timedOut &&
|
|
366
|
+
statusCode === undefined &&
|
|
347
367
|
String(errorMessage).trim().toLowerCase() === "connection error.";
|
|
348
368
|
const providerRetryable = typeof err?.isRetryable === "boolean"
|
|
349
369
|
? err.isRetryable
|
|
350
|
-
: isConnectionError
|
|
370
|
+
: isConnectionError || timedOut
|
|
351
371
|
? true
|
|
352
372
|
: undefined;
|
|
353
373
|
if (statusCode === 401) {
|
|
@@ -370,13 +390,16 @@ class AISDKEngine {
|
|
|
370
390
|
// auto-resume too — matching the Builder gateway path.
|
|
371
391
|
...(statusCode !== undefined
|
|
372
392
|
? { errorCode: `http_${statusCode}`, statusCode }
|
|
373
|
-
: isConnectionError
|
|
393
|
+
: isConnectionError || timedOut
|
|
374
394
|
? { errorCode: "provider_network_error" }
|
|
375
395
|
: {}),
|
|
376
396
|
...(providerRetryable !== undefined ? { providerRetryable } : {}),
|
|
377
397
|
};
|
|
378
398
|
throw err;
|
|
379
399
|
}
|
|
400
|
+
finally {
|
|
401
|
+
firstEventAbort.cleanup();
|
|
402
|
+
}
|
|
380
403
|
}
|
|
381
404
|
async createProviderModel(model) {
|
|
382
405
|
const pkg = PROVIDER_PACKAGES[this.provider];
|