@agent-native/core 0.183.0 → 0.184.0-nightly-20260921223836

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.
Files changed (65) hide show
  1. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +26 -1
  2. package/corpus/templates/content/shared/agent-readable.ts +7 -5
  3. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +45 -17
  4. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +8 -0
  5. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +22 -15
  6. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +69 -30
  7. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +10 -1
  8. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +3 -12
  9. package/corpus/templates/design/app/pages/design-editor/commands/nudge-selection.ts +37 -3
  10. package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +5 -2
  11. package/dist/chat-threads/store.d.ts +1 -0
  12. package/dist/chat-threads/store.js +19 -0
  13. package/dist/client/observability/ObservabilityDashboard.js +113 -3
  14. package/dist/client/observability/useObservability.d.ts +9 -0
  15. package/dist/client/observability/useObservability.js +12 -0
  16. package/dist/collab/awareness.d.ts +2 -2
  17. package/dist/file-upload/actions/upload-image.d.ts +3 -3
  18. package/dist/identity/rekey.js +5 -0
  19. package/dist/localization/default-messages.d.ts +24 -0
  20. package/dist/localization/default-messages.js +24 -0
  21. package/dist/notifications/routes.d.ts +3 -3
  22. package/dist/observability/actions/list-observability-reviews.d.ts +5 -0
  23. package/dist/observability/actions/list-observability-reviews.js +23 -0
  24. package/dist/observability/actions/save-observability-instruction-update.d.ts +9 -0
  25. package/dist/observability/actions/save-observability-instruction-update.js +38 -0
  26. package/dist/observability/index.d.ts +2 -2
  27. package/dist/observability/index.js +1 -1
  28. package/dist/observability/reviews.d.ts +6 -0
  29. package/dist/observability/reviews.js +155 -0
  30. package/dist/observability/routes.d.ts +6 -6
  31. package/dist/observability/store.d.ts +8 -1
  32. package/dist/observability/store.js +77 -0
  33. package/dist/observability/types.d.ts +23 -0
  34. package/dist/provider-api/actions/custom-provider-registration.d.ts +17 -17
  35. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  36. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  37. package/dist/secrets/routes.d.ts +6 -6
  38. package/dist/server/agent-engine-api-key-route.d.ts +2 -2
  39. package/dist/server/core-routes-plugin.js +4 -3
  40. package/dist/server/realtime-token.d.ts +1 -1
  41. package/dist/templates/default/app/i18n/ar-SA.ts +25 -0
  42. package/dist/templates/default/app/i18n/de-DE.ts +25 -0
  43. package/dist/templates/default/app/i18n/en-US.ts +26 -0
  44. package/dist/templates/default/app/i18n/es-ES.ts +26 -0
  45. package/dist/templates/default/app/i18n/fr-FR.ts +26 -0
  46. package/dist/templates/default/app/i18n/hi-IN.ts +25 -0
  47. package/dist/templates/default/app/i18n/ja-JP.ts +25 -0
  48. package/dist/templates/default/app/i18n/ko-KR.ts +25 -0
  49. package/dist/templates/default/app/i18n/pt-BR.ts +25 -0
  50. package/dist/templates/default/app/i18n/zh-CN.ts +24 -0
  51. package/dist/templates/default/app/i18n/zh-TW.ts +24 -0
  52. package/dist/triggers/actions/manage-automation.d.ts +2 -2
  53. package/dist/vite/action-types-plugin.js +8 -0
  54. package/package.json +5 -3
  55. package/src/templates/default/app/i18n/ar-SA.ts +25 -0
  56. package/src/templates/default/app/i18n/de-DE.ts +25 -0
  57. package/src/templates/default/app/i18n/en-US.ts +26 -0
  58. package/src/templates/default/app/i18n/es-ES.ts +26 -0
  59. package/src/templates/default/app/i18n/fr-FR.ts +26 -0
  60. package/src/templates/default/app/i18n/hi-IN.ts +25 -0
  61. package/src/templates/default/app/i18n/ja-JP.ts +25 -0
  62. package/src/templates/default/app/i18n/ko-KR.ts +25 -0
  63. package/src/templates/default/app/i18n/pt-BR.ts +25 -0
  64. package/src/templates/default/app/i18n/zh-CN.ts +24 -0
  65. package/src/templates/default/app/i18n/zh-TW.ts +24 -0
@@ -406,6 +406,25 @@ export async function resolveThreadAccess(userEmail, threadId, minRole = "viewer
406
406
  return null;
407
407
  return await getThread(threadId);
408
408
  }
409
+ export async function resolveThreadsAccess(userEmail, threadIds, ctx = {}) {
410
+ const ids = [...new Set(threadIds.filter(Boolean))];
411
+ const threads = new Map();
412
+ if (!userEmail || ids.length === 0)
413
+ return threads;
414
+ await ensureTable();
415
+ const access = chatThreadAccessSql(userEmail, ctx.orgId);
416
+ const client = getDbExec();
417
+ const placeholders = ids.map(() => "?").join(", ");
418
+ const { rows } = await client.execute({
419
+ sql: `SELECT ${THREAD_COLUMNS} FROM chat_threads WHERE id IN (${placeholders}) AND ${access.sql}`,
420
+ args: [...ids, ...access.args],
421
+ });
422
+ for (const row of rows) {
423
+ const thread = rowToThread(row);
424
+ threads.set(thread.id, thread);
425
+ }
426
+ return threads;
427
+ }
409
428
  export async function getThread(id) {
410
429
  await ensureTable();
411
430
  const client = getDbExec();
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { IconActivity, IconMessages, IconThumbUp, IconThumbDown, IconClock, IconCoin, IconTool, IconMoodSmile, IconChartBar, IconAB2, IconMessageReport, IconChevronRight, IconArrowLeft, IconLoader2, IconAlertTriangle, } from "@tabler/icons-react";
3
- import { useState } from "react";
3
+ import { useQueryClient } from "@tanstack/react-query";
4
+ import { Fragment, useEffect, useState } from "react";
4
5
  import { useT } from "../i18n.js";
5
6
  import { cn } from "../utils.js";
6
- import { useObservabilityOverview, useTraces, useTraceDetail, useFeedbackList, useFeedbackStats, useSatisfaction, useEvalStats, useExperiments, useExperimentDetail, useExperimentResults, } from "./useObservability.js";
7
+ import { useObservabilityOverview, useTraces, useTraceDetail, useFeedbackList, useFeedbackStats, useSatisfaction, useEvalStats, useExperiments, useExperimentDetail, useExperimentResults, useOutputReviews, useSaveInstructionUpdate, useSubmitFeedback, } from "./useObservability.js";
7
8
  // ─── Helpers ────────────────────────────────────────────────────────────
8
9
  function formatCost(centsX100) {
9
10
  const cents = centsX100 / 100;
@@ -138,6 +139,110 @@ function ExperimentDetailView({ id, onBack, }) {
138
139
  const { data: results } = useExperimentResults(id);
139
140
  return (_jsxs("div", { children: [_jsxs("button", { onClick: onBack, className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground mb-3", children: [_jsx(IconArrowLeft, { size: 14 }), t("observability.backToExperiments")] }), isLoading && _jsx(LoadingState, {}), exp && (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between gap-2 min-w-0", children: [_jsx("h3", { className: "text-sm font-medium text-foreground truncate min-w-0", children: exp.name }), _jsx(StatusBadge, { status: exp.status })] }), _jsxs("div", { className: "grid grid-cols-3 gap-3", children: [_jsxs("div", { className: "rounded-lg border border-border p-3", children: [_jsx("div", { className: "text-[10px] text-muted-foreground mb-1", children: t("observability.variants") }), _jsx("div", { className: "text-sm font-medium tabular-nums text-foreground", children: exp.variants.length })] }), _jsxs("div", { className: "rounded-lg border border-border p-3", children: [_jsx("div", { className: "text-[10px] text-muted-foreground mb-1", children: t("observability.metrics") }), _jsx("div", { className: "text-sm font-medium tabular-nums text-foreground", children: exp.metrics.length })] }), _jsxs("div", { className: "rounded-lg border border-border p-3", children: [_jsx("div", { className: "text-[10px] text-muted-foreground mb-1", children: t("observability.level") }), _jsx("div", { className: "text-sm font-medium text-foreground capitalize", children: exp.assignmentLevel })] })] }), exp.variants.length > 0 && (_jsxs("div", { children: [_jsx("h4", { className: "text-xs font-medium text-foreground mb-2", children: t("observability.variants") }), _jsx("div", { className: "space-y-1", children: exp.variants.map((v) => (_jsxs("div", { className: "flex items-center justify-between rounded border border-border px-3 py-2 text-xs", children: [_jsx("span", { className: "font-mono text-foreground", children: truncateId(v.id) }), _jsxs("span", { className: "text-muted-foreground tabular-nums", children: ["Weight: ", v.weight] })] }, v.id))) })] })), results && results.length > 0 && (_jsxs("div", { children: [_jsx("h4", { className: "text-xs font-medium text-foreground mb-2", children: t("observability.results") }), _jsx("div", { className: "rounded-lg border border-border overflow-hidden", children: _jsxs("table", { className: "w-full table-fixed text-left text-xs", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-border bg-muted/30", children: [_jsx("th", { className: "px-3 py-2 font-medium text-muted-foreground w-[20%]", children: t("observability.variant") }), _jsx("th", { className: "px-3 py-2 font-medium text-muted-foreground w-[25%]", children: t("observability.metric") }), _jsx("th", { className: "px-3 py-2 font-medium text-muted-foreground", children: t("observability.value") }), _jsx("th", { className: "px-3 py-2 font-medium text-muted-foreground", children: "CI" }), _jsx("th", { className: "px-3 py-2 font-medium text-muted-foreground", children: "N" })] }) }), _jsx("tbody", { children: results.map((r) => (_jsxs("tr", { className: "border-b border-border last:border-b-0", children: [_jsx("td", { className: "px-3 py-2 font-mono text-foreground truncate", children: truncateId(r.variantId) }), _jsx("td", { className: "px-3 py-2 text-foreground truncate", children: r.metric }), _jsx("td", { className: "px-3 py-2 tabular-nums text-foreground", children: r.value.toFixed(3) }), _jsxs("td", { className: "px-3 py-2 tabular-nums text-muted-foreground", children: ["[", r.confidenceLow.toFixed(3), ",", " ", r.confidenceHigh.toFixed(3), "]"] }), _jsx("td", { className: "px-3 py-2 tabular-nums text-muted-foreground", children: r.sampleSize })] }, r.id))) })] }) })] }))] }))] }));
140
141
  }
142
+ // ─── Tab: Human review ─────────────────────────────────────────────────
143
+ function ReviewTab({ days }) {
144
+ const t = useT();
145
+ const { data: reviews, isLoading } = useOutputReviews(days);
146
+ const feedbackMutation = useSubmitFeedback();
147
+ const instructionMutation = useSaveInstructionUpdate();
148
+ const queryClient = useQueryClient();
149
+ const [selectedRunId, setSelectedRunId] = useState(null);
150
+ const [feedbackNote, setFeedbackNote] = useState("");
151
+ const [instruction, setInstruction] = useState("");
152
+ const [instructionSaved, setInstructionSaved] = useState(false);
153
+ const [target, setTarget] = useState("agent");
154
+ const selected = reviews?.find((review) => review.runId === selectedRunId);
155
+ useEffect(() => {
156
+ if (selectedRunId &&
157
+ reviews &&
158
+ !reviews.some((review) => review.runId === selectedRunId)) {
159
+ setSelectedRunId(null);
160
+ }
161
+ }, [reviews, selectedRunId]);
162
+ if (isLoading)
163
+ return _jsx(LoadingState, {});
164
+ if (!reviews || reviews.length === 0) {
165
+ return _jsx(EmptyState, { message: t("observability.noReviews") });
166
+ }
167
+ const openReview = (runId) => {
168
+ setSelectedRunId((current) => (current === runId ? null : runId));
169
+ setFeedbackNote("");
170
+ setInstruction("");
171
+ setInstructionSaved(false);
172
+ setTarget("agent");
173
+ };
174
+ const saveFeedback = (feedbackType) => {
175
+ if (!selected)
176
+ return;
177
+ feedbackMutation.mutate({
178
+ runId: selected.runId,
179
+ threadId: selected.threadId ?? undefined,
180
+ feedbackType,
181
+ }, {
182
+ onSuccess: () => void queryClient.invalidateQueries({
183
+ queryKey: ["action", "list-observability-reviews"],
184
+ }),
185
+ });
186
+ };
187
+ const saveNote = () => {
188
+ if (!selected || !feedbackNote.trim())
189
+ return;
190
+ feedbackMutation.mutate({
191
+ runId: selected.runId,
192
+ threadId: selected.threadId ?? undefined,
193
+ feedbackType: "text",
194
+ value: feedbackNote.trim(),
195
+ }, {
196
+ onSuccess: () => {
197
+ setFeedbackNote("");
198
+ void queryClient.invalidateQueries({
199
+ queryKey: ["action", "list-observability-reviews"],
200
+ });
201
+ },
202
+ });
203
+ };
204
+ const saveInstruction = () => {
205
+ if (!selected || !instruction.trim())
206
+ return;
207
+ instructionMutation.mutate({
208
+ runId: selected.runId,
209
+ threadId: selected.threadId,
210
+ target,
211
+ instruction: instruction.trim(),
212
+ feedback: feedbackNote.trim() || undefined,
213
+ }, {
214
+ onSuccess: () => {
215
+ setFeedbackNote("");
216
+ setInstruction("");
217
+ setInstructionSaved(true);
218
+ setTarget("agent");
219
+ void queryClient.invalidateQueries({
220
+ queryKey: ["action", "list-observability-reviews"],
221
+ });
222
+ },
223
+ });
224
+ };
225
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx("div", { className: "rounded-lg border border-border bg-muted/20 px-3 py-2 text-xs text-muted-foreground", children: t("observability.reviewDescription") }), _jsx("div", { className: "overflow-x-auto rounded-lg border border-border", children: _jsxs("table", { className: "w-full min-w-[760px] table-fixed text-left text-xs", children: [_jsx("thead", { className: "bg-muted/30 text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "w-[28%] px-3 py-2 font-medium", children: t("observability.ask") }), _jsx("th", { className: "w-[40%] px-3 py-2 font-medium", children: t("observability.answer") }), _jsx("th", { className: "w-[16%] px-3 py-2 font-medium", children: t("observability.reviewFeedback") }), _jsx("th", { className: "w-[16%] px-3 py-2 font-medium" })] }) }), _jsx("tbody", { children: reviews.map((review) => {
226
+ const isSelected = review.runId === selectedRunId;
227
+ const latestFeedback = review.feedback[0];
228
+ return (_jsxs(Fragment, { children: [_jsxs("tr", { className: "border-t border-border align-top", children: [_jsx("td", { className: "px-3 py-3 text-foreground", children: _jsx("div", { className: "line-clamp-3 break-words", children: review.ask || "-" }) }), _jsx("td", { className: "px-3 py-3 text-foreground", children: _jsx("div", { className: "line-clamp-3 break-words", children: review.answer || "-" }) }), _jsx("td", { className: "px-3 py-3 text-muted-foreground", children: latestFeedback
229
+ ? latestFeedback.feedbackType === "text"
230
+ ? t("observability.noteSaved")
231
+ : latestFeedback.feedbackType === "thumbs_up"
232
+ ? t("observability.looksGood")
233
+ : t("observability.needsChange")
234
+ : t("observability.notReviewed") }), _jsx("td", { className: "px-3 py-3 text-right", children: _jsx("button", { type: "button", onClick: () => openReview(review.runId), className: "rounded-md border border-border px-2.5 py-1.5 font-medium text-foreground hover:bg-muted", children: isSelected
235
+ ? t("observability.closeReview")
236
+ : t("observability.reviewOutput") }) })] }), isSelected && selected && (_jsx("tr", { className: "border-t border-border", children: _jsx("td", { colSpan: 4, className: "bg-muted/10 p-4", children: _jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("div", { className: "mb-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: t("observability.ask") }), _jsx("p", { className: "whitespace-pre-wrap break-words text-sm text-foreground", children: selected.ask || "-" })] }), _jsxs("div", { children: [_jsx("div", { className: "mb-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: t("observability.answer") }), _jsx("p", { className: "max-h-48 overflow-y-auto whitespace-pre-wrap break-words text-sm text-foreground", children: selected.answer || "-" })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx("button", { type: "button", onClick: () => saveFeedback("thumbs_up"), className: "rounded-md border border-border px-2.5 py-1.5 text-xs text-foreground hover:bg-muted", children: t("observability.looksGood") }), _jsx("button", { type: "button", onClick: () => saveFeedback("thumbs_down"), className: "rounded-md border border-border px-2.5 py-1.5 text-xs text-foreground hover:bg-muted", children: t("observability.needsChange") })] })] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-xs font-medium text-foreground", children: t("observability.feedbackNote") }), _jsx("textarea", { value: feedbackNote, onChange: (event) => setFeedbackNote(event.target.value), rows: 3, className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:ring-2 focus:ring-ring", placeholder: t("observability.feedbackPlaceholder") })] }), _jsx("button", { type: "button", onClick: saveNote, disabled: !feedbackNote.trim() ||
237
+ feedbackMutation.isPending, className: "rounded-md bg-primary px-2.5 py-1.5 text-xs font-medium text-primary-foreground disabled:opacity-50", children: t("observability.saveFeedback") }), _jsxs("div", { className: "border-t border-border pt-3", children: [_jsx("div", { className: "mb-1 text-xs font-medium text-foreground", children: t("observability.updateInstructions") }), _jsx("p", { className: "mb-2 text-[11px] text-muted-foreground", children: t("observability.draftNotice") }), _jsx("div", { className: "flex gap-2", children: _jsxs("select", { value: target, onChange: (event) => setTarget(event.target.value), className: "rounded-md border border-border bg-background px-2 py-1.5 text-xs text-foreground", "aria-label": t("observability.instructionTarget"), children: [_jsx("option", { value: "agent", children: t("observability.agentTarget") }), _jsx("option", { value: "developer", children: t("observability.developerTarget") }), _jsx("option", { value: "skill", children: t("observability.skillTarget") })] }) }), _jsx("textarea", { value: instruction, onChange: (event) => {
238
+ setInstruction(event.target.value);
239
+ setInstructionSaved(false);
240
+ }, rows: 4, className: "mt-2 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:ring-2 focus:ring-ring", placeholder: t("observability.instructionPlaceholder") }), _jsx("button", { type: "button", onClick: saveInstruction, disabled: !instruction.trim() ||
241
+ instructionMutation.isPending, className: "mt-2 rounded-md border border-border px-2.5 py-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:opacity-50", children: instructionSaved
242
+ ? t("observability.draftSaved")
243
+ : t("observability.saveUpdate") })] })] })] }) }) }))] }, review.runId));
244
+ }) })] }) })] }));
245
+ }
141
246
  // ─── Tab: Feedback ──────────────────────────────────────────────────────
142
247
  function FeedbackTab({ days }) {
143
248
  const t = useT();
@@ -177,6 +282,11 @@ const TABS = [
177
282
  labelKey: "observability.feedback",
178
283
  icon: IconMessageReport,
179
284
  },
285
+ {
286
+ id: "review",
287
+ labelKey: "observability.review",
288
+ icon: IconMessageReport,
289
+ },
180
290
  ];
181
291
  export function ObservabilityDashboard({ className, }) {
182
292
  const t = useT();
@@ -187,5 +297,5 @@ export function ObservabilityDashboard({ className, }) {
187
297
  return (_jsxs("button", { onClick: () => setActiveTab(tab.id), className: cn("flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium", activeTab === tab.id
188
298
  ? "bg-background text-foreground shadow-sm"
189
299
  : "text-muted-foreground hover:text-foreground"), children: [_jsx(Icon, { size: 14 }), t(tab.labelKey)] }, tab.id));
190
- }) }), activeTab !== "experiments" && (_jsx(RangeSelector, { value: days, onChange: setDays }))] }), activeTab === "overview" && _jsx(OverviewTab, { days: days }), activeTab === "conversations" && _jsx(ConversationsTab, { days: days }), activeTab === "evals" && _jsx(EvalsTab, { days: days }), activeTab === "experiments" && _jsx(ExperimentsTab, {}), activeTab === "feedback" && _jsx(FeedbackTab, { days: days })] }));
300
+ }) }), activeTab !== "experiments" && (_jsx(RangeSelector, { value: days, onChange: setDays }))] }), activeTab === "overview" && _jsx(OverviewTab, { days: days }), activeTab === "conversations" && _jsx(ConversationsTab, { days: days }), activeTab === "evals" && _jsx(EvalsTab, { days: days }), activeTab === "experiments" && _jsx(ExperimentsTab, {}), activeTab === "feedback" && _jsx(FeedbackTab, { days: days }), activeTab === "review" && _jsx(ReviewTab, { days: days })] }));
191
301
  }
@@ -1,3 +1,4 @@
1
+ import type { InstructionUpdate, OutputReviewRow } from "../../observability/types.js";
1
2
  export interface ObservabilityOverview {
2
3
  totalRuns: number;
3
4
  totalCostCents: number;
@@ -24,6 +25,14 @@ export interface TraceSummary {
24
25
  createdAt: number;
25
26
  }
26
27
  export declare function useTraces(sinceDays?: number, limit?: number): import("@tanstack/react-query").UseQueryResult<NoInfer<TraceSummary[]>, Error>;
28
+ export declare function useOutputReviews(sinceDays?: number, limit?: number): import("@tanstack/react-query").UseQueryResult<NoInfer<OutputReviewRow[]>, Error>;
29
+ export declare function useSaveInstructionUpdate(): import("@tanstack/react-query").UseMutationResult<InstructionUpdate, Error, {
30
+ runId: string;
31
+ threadId?: string | null;
32
+ target: "agent" | "developer" | "skill";
33
+ instruction: string;
34
+ feedback?: string;
35
+ }, unknown>;
27
36
  export interface TraceSpan {
28
37
  id: string;
29
38
  runId: string;
@@ -1,5 +1,7 @@
1
1
  import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
2
+ import { useMemo } from "react";
2
3
  import { agentNativePath } from "../api-path.js";
4
+ import { useActionMutation, useActionQuery } from "../use-action.js";
3
5
  const BASE = agentNativePath("/_agent-native/observability");
4
6
  function fetchJson(url) {
5
7
  return fetch(url).then((r) => {
@@ -24,6 +26,16 @@ export function useTraces(sinceDays = 7, limit = 100) {
24
26
  refetchInterval: 30_000,
25
27
  });
26
28
  }
29
+ export function useOutputReviews(sinceDays = 7, limit = 100) {
30
+ const params = useMemo(() => ({
31
+ sinceMs: Date.now() - sinceDays * 86_400_000,
32
+ limit,
33
+ }), [sinceDays, limit]);
34
+ return useActionQuery("list-observability-reviews", params, { refetchInterval: 30_000 });
35
+ }
36
+ export function useSaveInstructionUpdate() {
37
+ return useActionMutation("save-observability-instruction-update");
38
+ }
27
39
  export function useTraceDetail(runId) {
28
40
  return useQuery({
29
41
  queryKey: ["observability", "trace", runId],
@@ -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,9 +77,9 @@ 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
  }>>;
@@ -36,23 +36,23 @@ declare const _default: import("../../action.js").ActionDefinition<{
36
36
  } | {
37
37
  error: string;
38
38
  url?: undefined;
39
+ id?: undefined;
39
40
  provider?: undefined;
40
41
  configured?: undefined;
41
42
  connectPath?: undefined;
42
- id?: undefined;
43
43
  } | {
44
44
  url: string;
45
45
  id: string;
46
46
  provider: string;
47
+ error?: undefined;
47
48
  configured?: undefined;
48
49
  connectPath?: undefined;
49
- error?: undefined;
50
50
  } | {
51
51
  url?: undefined;
52
+ id?: undefined;
52
53
  provider?: undefined;
53
54
  error: string;
54
55
  configured: boolean;
55
56
  connectPath: string;
56
- id?: undefined;
57
57
  }>;
58
58
  export default _default;
@@ -28,6 +28,11 @@ export const IDENTITY_REKEY_COLUMNS = [
28
28
  column: "user_id",
29
29
  mode: "email-user-id",
30
30
  },
31
+ {
32
+ table: "agent_instruction_updates",
33
+ column: "user_id",
34
+ mode: "email-user-id",
35
+ },
31
36
  { table: "agent_evals", column: "user_id", mode: "email-user-id" },
32
37
  {
33
38
  table: "agent_experiment_assignments",
@@ -1547,6 +1547,30 @@ declare const messages: {
1547
1547
  frustration: string;
1548
1548
  thumbsUpRate: string;
1549
1549
  categories: string;
1550
+ review: string;
1551
+ reviewDescription: string;
1552
+ ask: string;
1553
+ answer: string;
1554
+ reviewFeedback: string;
1555
+ reviewOutput: string;
1556
+ closeReview: string;
1557
+ notReviewed: string;
1558
+ noteSaved: string;
1559
+ looksGood: string;
1560
+ needsChange: string;
1561
+ feedbackNote: string;
1562
+ feedbackPlaceholder: string;
1563
+ saveFeedback: string;
1564
+ updateInstructions: string;
1565
+ draftNotice: string;
1566
+ instructionTarget: string;
1567
+ agentTarget: string;
1568
+ developerTarget: string;
1569
+ skillTarget: string;
1570
+ instructionPlaceholder: string;
1571
+ saveUpdate: string;
1572
+ draftSaved: string;
1573
+ noReviews: string;
1550
1574
  };
1551
1575
  error: {
1552
1576
  genericTitle: string;
@@ -1554,6 +1554,30 @@ const messages = {
1554
1554
  frustration: "Frustration",
1555
1555
  thumbsUpRate: "Thumbs up rate",
1556
1556
  categories: "Categories",
1557
+ review: "Human review",
1558
+ reviewDescription: "Review the ask and answer, record feedback, and draft an instruction update.",
1559
+ ask: "What was asked",
1560
+ answer: "What the agent answered",
1561
+ reviewFeedback: "Feedback",
1562
+ reviewOutput: "Review output",
1563
+ closeReview: "Close",
1564
+ notReviewed: "Not reviewed",
1565
+ noteSaved: "Note saved",
1566
+ looksGood: "Looks good",
1567
+ needsChange: "Needs change",
1568
+ feedbackNote: "Feedback note",
1569
+ feedbackPlaceholder: "What should change or stay the same?",
1570
+ saveFeedback: "Save feedback",
1571
+ updateInstructions: "Update instructions",
1572
+ draftNotice: "Draft only - nothing changes automatically.",
1573
+ instructionTarget: "Instruction target",
1574
+ agentTarget: "Agent",
1575
+ developerTarget: "Developer",
1576
+ skillTarget: "Skill",
1577
+ instructionPlaceholder: "Write the instruction change for a human to review.",
1578
+ saveUpdate: "Save draft update",
1579
+ draftSaved: "Draft saved",
1580
+ noReviews: "No agent outputs to review yet",
1557
1581
  },
1558
1582
  error: {
1559
1583
  genericTitle: "Something went wrong",
@@ -11,14 +11,14 @@
11
11
  * DELETE /_agent-native/notifications/:id — delete
12
12
  */
13
13
  export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
14
+ error?: undefined;
14
15
  count: number;
15
16
  updated?: undefined;
16
- error?: undefined;
17
17
  ok?: undefined;
18
18
  } | {
19
+ error?: undefined;
19
20
  count?: undefined;
20
21
  updated: number;
21
- error?: undefined;
22
22
  ok?: undefined;
23
23
  } | {
24
24
  count?: undefined;
@@ -26,8 +26,8 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
26
26
  error: string;
27
27
  ok?: undefined;
28
28
  } | {
29
+ error?: undefined;
29
30
  count?: undefined;
30
31
  updated?: undefined;
31
32
  ok: boolean;
32
- error?: undefined;
33
33
  }>>;
@@ -0,0 +1,5 @@
1
+ declare const _default: import("../../action.js").ActionDefinition<{
2
+ sinceMs?: number | undefined;
3
+ limit?: number | undefined;
4
+ }, import("../types.js").OutputReviewRow[]>;
5
+ export default _default;
@@ -0,0 +1,23 @@
1
+ import { z } from "zod";
2
+ import { fail, defineAction } from "../../action.js";
3
+ import { listOutputReviews } from "../reviews.js";
4
+ export default defineAction({
5
+ description: "List recent agent outputs as a human-review table with the original ask, answer, feedback, and pending instruction updates.",
6
+ schema: z.object({
7
+ sinceMs: z.number().int().nonnegative().optional(),
8
+ limit: z.number().int().positive().max(100).optional(),
9
+ }),
10
+ http: { method: "GET" },
11
+ readOnly: true,
12
+ parallelSafe: true,
13
+ run: async (args, ctx) => {
14
+ const userId = ctx?.userEmail;
15
+ if (!userId)
16
+ fail("Sign in to review agent outputs.", { statusCode: 401 });
17
+ return listOutputReviews({
18
+ sinceMs: args.sinceMs ?? Date.now() - 7 * 86_400_000,
19
+ limit: args.limit ?? 100,
20
+ userId,
21
+ });
22
+ },
23
+ });
@@ -0,0 +1,9 @@
1
+ import type { InstructionUpdate } from "../types.js";
2
+ declare const _default: import("../../action.js").ActionDefinition<{
3
+ runId: string;
4
+ threadId?: string | null | undefined;
5
+ target: "agent" | "developer" | "skill";
6
+ instruction: string;
7
+ feedback?: string | undefined;
8
+ }, InstructionUpdate>;
9
+ export default _default;
@@ -0,0 +1,38 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { fail, defineAction } from "../../action.js";
4
+ import { getTraceSummary, insertInstructionUpdate } from "../store.js";
5
+ const schema = z.object({
6
+ runId: z.string().trim().min(1).max(200),
7
+ threadId: z.string().trim().max(200).nullable().optional(),
8
+ target: z.enum(["agent", "developer", "skill"]),
9
+ instruction: z.string().trim().min(1).max(10_000),
10
+ feedback: z.string().trim().max(4_000).optional(),
11
+ });
12
+ export default defineAction({
13
+ description: "Save a human-proposed instruction update for an agent output. The draft is explicit and never applied automatically.",
14
+ schema,
15
+ run: async (args, ctx) => {
16
+ const userId = ctx?.userEmail;
17
+ if (!userId)
18
+ fail("Sign in to save instruction updates.", { statusCode: 401 });
19
+ const summary = await getTraceSummary(args.runId, { userId });
20
+ if (!summary)
21
+ fail("That agent output is no longer available.", { statusCode: 404 });
22
+ const now = Date.now();
23
+ const update = {
24
+ id: `instruction-${randomUUID()}`,
25
+ runId: args.runId,
26
+ threadId: args.threadId ?? summary.threadId,
27
+ target: args.target,
28
+ instruction: args.instruction,
29
+ feedback: args.feedback ?? "",
30
+ status: "draft",
31
+ userId,
32
+ createdAt: now,
33
+ updatedAt: now,
34
+ };
35
+ await insertInstructionUpdate(update);
36
+ return update;
37
+ },
38
+ });
@@ -1,5 +1,5 @@
1
- export type { SpanType, SpanStatus, TraceSpan, TraceSummary, FeedbackType, FeedbackEntry, SatisfactionScore, EvalType, EvalResult, EvalDataset, EvalTestCase, EvalCriteria, ExperimentStatus, ExperimentVariant, Experiment, ExperimentAssignment, ExperimentMetricResult, ObservabilityConfig, } from "./types.js";
2
- export { ensureObservabilityTables, insertTraceSpan, upsertTraceSummary, getTraceSpansForRun, getTraceSummaries, getTraceSummary, deleteOldTraceData, insertFeedback, getFeedback, getFeedbackStats, upsertSatisfactionScore, getSatisfactionScores, insertEvalResult, getEvalsForRun, getEvalStats, insertEvalDataset, listEvalDatasets, getEvalDataset, updateEvalDataset, insertExperiment, updateExperiment, listExperiments, getExperiment, upsertAssignment, getAssignment, insertExperimentResult, getExperimentResults, getObservabilityOverview, } from "./store.js";
1
+ export type { SpanType, SpanStatus, TraceSpan, TraceSummary, FeedbackType, FeedbackEntry, InstructionUpdateStatus, InstructionUpdate, OutputReviewRow, SatisfactionScore, EvalType, EvalResult, EvalDataset, EvalTestCase, EvalCriteria, ExperimentStatus, ExperimentVariant, Experiment, ExperimentAssignment, ExperimentMetricResult, ObservabilityConfig, } from "./types.js";
2
+ export { ensureObservabilityTables, insertTraceSpan, upsertTraceSummary, getTraceSpansForRun, getTraceSummaries, getTraceSummary, deleteOldTraceData, insertFeedback, getFeedback, getFeedbackStats, insertInstructionUpdate, getInstructionUpdates, upsertSatisfactionScore, getSatisfactionScores, insertEvalResult, getEvalsForRun, getEvalStats, insertEvalDataset, listEvalDatasets, getEvalDataset, updateEvalDataset, insertExperiment, updateExperiment, listExperiments, getExperiment, upsertAssignment, getAssignment, insertExperimentResult, getExperimentResults, getObservabilityOverview, } from "./store.js";
3
3
  export { createObservabilityPlugin } from "./plugin.js";
4
4
  export { createObservabilityHandler } from "./routes.js";
5
5
  export { runTraceCleanupOnce, startTraceCleanupJob, stopTraceCleanupJob, } from "./cleanup-job.js";
@@ -1,4 +1,4 @@
1
- export { ensureObservabilityTables, insertTraceSpan, upsertTraceSummary, getTraceSpansForRun, getTraceSummaries, getTraceSummary, deleteOldTraceData, insertFeedback, getFeedback, getFeedbackStats, upsertSatisfactionScore, getSatisfactionScores, insertEvalResult, getEvalsForRun, getEvalStats, insertEvalDataset, listEvalDatasets, getEvalDataset, updateEvalDataset, insertExperiment, updateExperiment, listExperiments, getExperiment, upsertAssignment, getAssignment, insertExperimentResult, getExperimentResults, getObservabilityOverview, } from "./store.js";
1
+ export { ensureObservabilityTables, insertTraceSpan, upsertTraceSummary, getTraceSpansForRun, getTraceSummaries, getTraceSummary, deleteOldTraceData, insertFeedback, getFeedback, getFeedbackStats, insertInstructionUpdate, getInstructionUpdates, upsertSatisfactionScore, getSatisfactionScores, insertEvalResult, getEvalsForRun, getEvalStats, insertEvalDataset, listEvalDatasets, getEvalDataset, updateEvalDataset, insertExperiment, updateExperiment, listExperiments, getExperiment, upsertAssignment, getAssignment, insertExperimentResult, getExperimentResults, getObservabilityOverview, } from "./store.js";
2
2
  export { createObservabilityPlugin } from "./plugin.js";
3
3
  export { createObservabilityHandler } from "./routes.js";
4
4
  export { runTraceCleanupOnce, startTraceCleanupJob, stopTraceCleanupJob, } from "./cleanup-job.js";
@@ -0,0 +1,6 @@
1
+ import type { OutputReviewRow } from "./types.js";
2
+ export declare function listOutputReviews(opts: {
3
+ sinceMs: number;
4
+ limit: number;
5
+ userId: string;
6
+ }): Promise<OutputReviewRow[]>;
@@ -0,0 +1,155 @@
1
+ import { resolveThreadsAccess } from "../chat-threads/store.js";
2
+ import { getFeedback, getInstructionUpdates, getTraceSummaries, } from "./store.js";
3
+ function unwrapMessage(value) {
4
+ if (!value || typeof value !== "object" || Array.isArray(value))
5
+ return null;
6
+ const record = value;
7
+ const nested = record.message;
8
+ return nested && typeof nested === "object" && !Array.isArray(nested)
9
+ ? nested
10
+ : record;
11
+ }
12
+ function messageText(value) {
13
+ if (typeof value === "string")
14
+ return value;
15
+ if (!Array.isArray(value))
16
+ return "";
17
+ return value
18
+ .filter((part) => Boolean(part) &&
19
+ typeof part === "object" &&
20
+ !Array.isArray(part) &&
21
+ part.type === "text")
22
+ .map((part) => (typeof part.text === "string" ? part.text : ""))
23
+ .join("");
24
+ }
25
+ function messageRunId(message) {
26
+ const metadata = message.metadata;
27
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
28
+ return undefined;
29
+ }
30
+ const meta = metadata;
31
+ const custom = meta.custom;
32
+ const customRecord = custom && typeof custom === "object" && !Array.isArray(custom)
33
+ ? custom
34
+ : null;
35
+ const candidates = [
36
+ meta.runId,
37
+ customRecord?.runId,
38
+ customRecord?.submittedRunId,
39
+ ];
40
+ return candidates.find((candidate) => typeof candidate === "string" && candidate.length > 0);
41
+ }
42
+ function readThreadMessages(threadData) {
43
+ try {
44
+ const repository = JSON.parse(threadData);
45
+ const values = Array.isArray(repository?.messages)
46
+ ? repository.messages
47
+ : [];
48
+ return values.flatMap((value) => {
49
+ const message = unwrapMessage(value);
50
+ if (!message ||
51
+ (message.role !== "user" && message.role !== "assistant")) {
52
+ return [];
53
+ }
54
+ if (!Object.prototype.hasOwnProperty.call(message, "content")) {
55
+ return [];
56
+ }
57
+ const content = message.content;
58
+ if (typeof content !== "string" && !Array.isArray(content)) {
59
+ return [];
60
+ }
61
+ const text = messageText(content).trim();
62
+ return text
63
+ ? [{ role: message.role, text, runId: messageRunId(message) }]
64
+ : [];
65
+ });
66
+ }
67
+ catch (error) {
68
+ throw new Error("Unable to parse observability thread data", {
69
+ cause: error,
70
+ });
71
+ }
72
+ }
73
+ function askAndAnswer(summary, threadData) {
74
+ if (!threadData)
75
+ return { ask: "", answer: "" };
76
+ const messages = readThreadMessages(threadData);
77
+ const askIndex = messages.findIndex((message) => message.role === "user" && message.runId === summary.runId);
78
+ const resolvedAskIndex = askIndex >= 0
79
+ ? askIndex
80
+ : messages.filter((message) => message.role === "user").length === 1
81
+ ? messages.findIndex((message) => message.role === "user")
82
+ : -1;
83
+ const answerIndex = resolvedAskIndex < 0
84
+ ? -1
85
+ : messages.findIndex((message, index) => {
86
+ if (index <= resolvedAskIndex || message.role === "user") {
87
+ return false;
88
+ }
89
+ if (message.role !== "assistant")
90
+ return false;
91
+ return message.runId === summary.runId || !message.runId;
92
+ });
93
+ return {
94
+ ask: resolvedAskIndex >= 0 ? messages[resolvedAskIndex].text : "",
95
+ answer: answerIndex >= 0 ? messages[answerIndex].text : "",
96
+ };
97
+ }
98
+ export async function listOutputReviews(opts) {
99
+ const summaries = await getTraceSummaries({
100
+ sinceMs: opts.sinceMs,
101
+ limit: opts.limit,
102
+ userId: opts.userId,
103
+ });
104
+ const [feedback, updates] = await Promise.all([
105
+ getFeedback({
106
+ sinceMs: opts.sinceMs,
107
+ limit: opts.limit * 4,
108
+ userId: opts.userId,
109
+ }),
110
+ getInstructionUpdates({
111
+ sinceMs: opts.sinceMs,
112
+ limit: opts.limit * 2,
113
+ userId: opts.userId,
114
+ }),
115
+ ]);
116
+ const feedbackByRun = groupByRun(feedback);
117
+ const updateByRun = new Map();
118
+ for (const update of updates) {
119
+ if (!updateByRun.has(update.runId))
120
+ updateByRun.set(update.runId, update);
121
+ }
122
+ const threadIds = summaries.flatMap((summary) => summary.threadId ? [summary.threadId] : []);
123
+ const threads = await resolveThreadsAccess(opts.userId, threadIds);
124
+ return summaries
125
+ .map((summary) => {
126
+ const thread = summary.threadId
127
+ ? (threads.get(summary.threadId) ?? null)
128
+ : null;
129
+ if (summary.threadId && !thread)
130
+ return null;
131
+ const { ask, answer } = askAndAnswer(summary, thread?.threadData ?? null);
132
+ return {
133
+ runId: summary.runId,
134
+ threadId: summary.threadId,
135
+ ask,
136
+ answer,
137
+ model: summary.model,
138
+ createdAt: summary.createdAt,
139
+ feedback: feedbackByRun.get(summary.runId) ?? [],
140
+ instructionUpdate: updateByRun.get(summary.runId) ?? null,
141
+ };
142
+ })
143
+ .filter((row) => row !== null);
144
+ }
145
+ function groupByRun(entries) {
146
+ const grouped = new Map();
147
+ for (const entry of entries) {
148
+ if (!entry.runId)
149
+ continue;
150
+ const current = grouped.get(entry.runId) ?? [];
151
+ current.push(entry);
152
+ grouped.set(entry.runId, current);
153
+ }
154
+ return grouped;
155
+ }