@agent-native/core 0.171.1 → 0.172.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +4 -5
- package/corpus/templates/calendar/actions/create-event.ts +11 -3
- package/corpus/templates/calendar/app/components/calendar/AddCalendarDialog.tsx +56 -51
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +3 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +1 -1
- package/corpus/templates/calendar/app/components/calendar/PeopleSearchDialog.tsx +54 -51
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +1 -1
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +4 -1
- package/corpus/templates/calendar/app/hooks/use-people.ts +23 -13
- package/corpus/templates/calendar/app/lib/event-layout.ts +21 -6
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +3 -0
- package/corpus/templates/calendar/changelog/2026-08-24-overlapping-events-use-space-better.md +5 -0
- package/corpus/templates/calendar/server/lib/google-api.ts +9 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +59 -2
- package/corpus/templates/calendar/server/lib/people-search.ts +43 -28
- package/corpus/templates/calendar/shared/api.ts +2 -0
- package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +3 -3
- package/corpus/templates/mail/app/root.tsx +29 -0
- package/dist/agent/actions/set-tool-approval-policy.d.ts +8 -0
- package/dist/agent/actions/set-tool-approval-policy.js +26 -0
- package/dist/agent/harness/migrations.js +8 -0
- package/dist/agent/harness/runner.js +114 -33
- package/dist/agent/harness/store.d.ts +9 -0
- package/dist/agent/harness/store.js +87 -23
- package/dist/agent/production-agent.d.ts +3 -0
- package/dist/agent/production-agent.js +23 -1
- package/dist/agent/run-manager.js +40 -9
- package/dist/agent/tool-approval-migrations.d.ts +21 -0
- package/dist/agent/tool-approval-migrations.js +32 -0
- package/dist/agent/tool-approval-store.d.ts +11 -0
- package/dist/agent/tool-approval-store.js +76 -1
- package/dist/cli/code-agent-executor.js +20 -3
- package/dist/client/AgentPanel.d.ts +4 -1
- package/dist/client/AgentPanel.js +11 -3
- package/dist/client/AssistantChat.d.ts +5 -5
- package/dist/client/AssistantChat.js +24 -11
- package/dist/client/DefaultSpinner.d.ts +3 -1
- package/dist/client/DefaultSpinner.js +2 -2
- package/dist/client/chat/tool-call-display.d.ts +5 -5
- package/dist/client/chat/tool-call-display.js +27 -11
- package/dist/client/chat-first/apps-rail.d.ts +1 -1
- package/dist/client/chat-first/apps-rail.js +11 -8
- package/dist/client/chat-first/copy.js +1 -0
- package/dist/client/chat-first/types.d.ts +1 -0
- package/dist/client/resources/McpIntegrationDialog.js +5 -5
- package/dist/client/resources/mcp-integration-catalog.js +1 -1
- package/dist/coding-tools/index.d.ts +31 -0
- package/dist/coding-tools/index.js +139 -3
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.js +3 -0
- package/dist/localization/core-messages/de-DE.js +3 -0
- package/dist/localization/core-messages/en-US.d.ts +3 -0
- package/dist/localization/core-messages/en-US.js +3 -0
- package/dist/localization/core-messages/es-ES.js +3 -0
- package/dist/localization/core-messages/fr-FR.js +3 -0
- package/dist/localization/core-messages/hi-IN.js +3 -0
- package/dist/localization/core-messages/ja-JP.js +3 -0
- package/dist/localization/core-messages/ko-KR.js +3 -0
- package/dist/localization/core-messages/pt-BR.js +3 -0
- package/dist/localization/core-messages/zh-CN.js +3 -0
- package/dist/localization/core-messages/zh-TW.js +3 -0
- package/dist/localization/core-messages.d.ts +3 -0
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +7 -6
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
- package/dist/provider-api/actions/provider-api.d.ts +11 -11
- package/dist/server/action-discovery.js +5 -0
- package/dist/server/agent-chat/prompt-resources.d.ts +9 -0
- package/dist/server/agent-chat/prompt-resources.js +12 -2
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/release-schema.js +4 -1
- package/dist/triggers/dispatcher.d.ts +24 -0
- package/dist/triggers/dispatcher.js +76 -18
- package/dist/vite/action-types-plugin.js +4 -0
- package/package.json +2 -1
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
calendarDeleteEvent,
|
|
31
31
|
calendarPatchEvent,
|
|
32
32
|
calendarUpdateEvent,
|
|
33
|
+
isGoogleEventAbsentError,
|
|
33
34
|
peopleGetProfile,
|
|
34
35
|
} from "./google-api.js";
|
|
35
36
|
import { getCalendarProviderApiRuntime } from "./provider-api.js";
|
|
@@ -1707,8 +1708,14 @@ export async function deleteEvent(
|
|
|
1707
1708
|
return;
|
|
1708
1709
|
}
|
|
1709
1710
|
|
|
1710
|
-
const instanceStart =
|
|
1711
|
-
|
|
1711
|
+
const instanceStart =
|
|
1712
|
+
instance.originalStartTime?.dateTime ||
|
|
1713
|
+
instance.originalStartTime?.date ||
|
|
1714
|
+
instance.start?.dateTime ||
|
|
1715
|
+
instance.start?.date ||
|
|
1716
|
+
"";
|
|
1717
|
+
const isAllDay =
|
|
1718
|
+
!instance.originalStartTime?.dateTime && !instance.start?.dateTime;
|
|
1712
1719
|
|
|
1713
1720
|
// Compute UNTIL value (day before this instance)
|
|
1714
1721
|
const cutoff = new Date(instanceStart);
|
|
@@ -1748,6 +1755,56 @@ export async function deleteEvent(
|
|
|
1748
1755
|
{ recurrence: updatedRecurrence },
|
|
1749
1756
|
{ sendUpdates },
|
|
1750
1757
|
);
|
|
1758
|
+
|
|
1759
|
+
// Truncating the master does not remove materialized exceptions after the
|
|
1760
|
+
// cutoff, so remove those exceptions as well.
|
|
1761
|
+
const instanceStartMs = Date.parse(instanceStart);
|
|
1762
|
+
const exceptionIds = new Set<string>([googleEventId]);
|
|
1763
|
+
let pageToken: string | undefined;
|
|
1764
|
+
do {
|
|
1765
|
+
const response = await calendarListEvents(client.accessToken, "primary", {
|
|
1766
|
+
singleEvents: false,
|
|
1767
|
+
showDeleted: true,
|
|
1768
|
+
maxResults: 2500,
|
|
1769
|
+
pageToken,
|
|
1770
|
+
});
|
|
1771
|
+
for (const event of response?.items || []) {
|
|
1772
|
+
if (event.recurringEventId !== recurringEventId) continue;
|
|
1773
|
+
const originalStart =
|
|
1774
|
+
event.originalStartTime?.dateTime ||
|
|
1775
|
+
event.originalStartTime?.date ||
|
|
1776
|
+
event.start?.dateTime ||
|
|
1777
|
+
event.start?.date;
|
|
1778
|
+
const originalStartMs =
|
|
1779
|
+
typeof originalStart === "string" ? Date.parse(originalStart) : NaN;
|
|
1780
|
+
if (
|
|
1781
|
+
event.id &&
|
|
1782
|
+
Number.isFinite(instanceStartMs) &&
|
|
1783
|
+
Number.isFinite(originalStartMs) &&
|
|
1784
|
+
originalStartMs >= instanceStartMs
|
|
1785
|
+
) {
|
|
1786
|
+
exceptionIds.add(event.id);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
pageToken =
|
|
1790
|
+
typeof response?.nextPageToken === "string"
|
|
1791
|
+
? response.nextPageToken
|
|
1792
|
+
: undefined;
|
|
1793
|
+
} while (pageToken);
|
|
1794
|
+
|
|
1795
|
+
for (const eventId of exceptionIds) {
|
|
1796
|
+
try {
|
|
1797
|
+
await calendarDeleteEvent(
|
|
1798
|
+
client.accessToken,
|
|
1799
|
+
"primary",
|
|
1800
|
+
eventId,
|
|
1801
|
+
sendUpdates,
|
|
1802
|
+
);
|
|
1803
|
+
} catch (error) {
|
|
1804
|
+
// A generated occurrence may already be gone once the master is trimmed.
|
|
1805
|
+
if (!isGoogleEventAbsentError(error)) throw error;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1751
1808
|
}
|
|
1752
1809
|
|
|
1753
1810
|
/**
|
|
@@ -36,6 +36,7 @@ interface ContactCacheEntry {
|
|
|
36
36
|
|
|
37
37
|
const CONTACT_CACHE_TTL = 10 * 60 * 1000;
|
|
38
38
|
const contactCache = new Map<string, ContactCacheEntry>();
|
|
39
|
+
const contactCacheLoads = new Map<string, Promise<ContactCacheEntry>>();
|
|
39
40
|
|
|
40
41
|
const GENERIC_EMAIL_DOMAINS = new Set([
|
|
41
42
|
"gmail.com",
|
|
@@ -255,36 +256,50 @@ async function loadCachedContactPeople(
|
|
|
255
256
|
return cached;
|
|
256
257
|
}
|
|
257
258
|
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
259
|
+
const pending = contactCacheLoads.get(cacheKey);
|
|
260
|
+
if (pending) return pending;
|
|
261
|
+
|
|
262
|
+
const load = (async () => {
|
|
263
|
+
const people = new Map<string, PersonResult>();
|
|
264
|
+
let contactsLimited = false;
|
|
265
|
+
|
|
266
|
+
await Promise.all(
|
|
267
|
+
clients.map(async (client) => {
|
|
268
|
+
try {
|
|
269
|
+
const [connections, otherContacts] = await Promise.all([
|
|
270
|
+
listConnectionPages(client.accessToken, "connections"),
|
|
271
|
+
listConnectionPages(client.accessToken, "otherContacts"),
|
|
272
|
+
]);
|
|
273
|
+
|
|
274
|
+
for (const person of extractPeople(connections, "contact")) {
|
|
275
|
+
mergeCachedContact(people, person);
|
|
276
|
+
}
|
|
277
|
+
for (const person of extractPeople(otherContacts, "otherContact")) {
|
|
278
|
+
mergeCachedContact(people, person);
|
|
279
|
+
}
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if (permissionLimited(error)) contactsLimited = true;
|
|
274
282
|
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
}),
|
|
279
|
-
);
|
|
283
|
+
}),
|
|
284
|
+
);
|
|
280
285
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
286
|
+
const entry: ContactCacheEntry = {
|
|
287
|
+
people: Array.from(people.values()),
|
|
288
|
+
contactsLimited,
|
|
289
|
+
expiresAt: Date.now() + CONTACT_CACHE_TTL,
|
|
290
|
+
};
|
|
291
|
+
contactCache.set(cacheKey, entry);
|
|
292
|
+
return entry;
|
|
293
|
+
})();
|
|
294
|
+
|
|
295
|
+
contactCacheLoads.set(cacheKey, load);
|
|
296
|
+
try {
|
|
297
|
+
return await load;
|
|
298
|
+
} finally {
|
|
299
|
+
if (contactCacheLoads.get(cacheKey) === load) {
|
|
300
|
+
contactCacheLoads.delete(cacheKey);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
288
303
|
}
|
|
289
304
|
|
|
290
305
|
export async function searchPeopleForUser(
|
|
@@ -66,6 +66,8 @@ export interface CalendarEvent {
|
|
|
66
66
|
hangoutLink?: string; // Google Meet link
|
|
67
67
|
/** Meeting URL stored in location/description for non-Google providers such as Zoom */
|
|
68
68
|
meetingLink?: string;
|
|
69
|
+
/** Action-result warning when optional video conferencing could not be provisioned. */
|
|
70
|
+
videoConferenceError?: "zoom";
|
|
69
71
|
conferenceData?: {
|
|
70
72
|
entryPoints?: Array<{
|
|
71
73
|
entryPointType: string;
|
|
@@ -103,9 +103,9 @@ export function PlaybackCommentOverlay({
|
|
|
103
103
|
</p>
|
|
104
104
|
<InlineMarkdown
|
|
105
105
|
content={comment.content}
|
|
106
|
-
className="line-clamp-3 text-sm leading-5 text-
|
|
107
|
-
linkClassName="text-
|
|
108
|
-
codeClassName="bg-
|
|
106
|
+
className="line-clamp-3 text-sm leading-5 text-background dark:text-foreground"
|
|
107
|
+
linkClassName="text-background underline decoration-background/60 hover:decoration-background dark:text-foreground dark:decoration-foreground/60 dark:hover:decoration-foreground"
|
|
108
|
+
codeClassName="bg-background/15 text-background dark:bg-foreground/15 dark:text-foreground"
|
|
109
109
|
/>
|
|
110
110
|
</div>
|
|
111
111
|
</div>
|
|
@@ -14,6 +14,11 @@ import {
|
|
|
14
14
|
type LocaleCode,
|
|
15
15
|
} from "@agent-native/core/client/i18n";
|
|
16
16
|
import {
|
|
17
|
+
isDynamicImportFailureMessage,
|
|
18
|
+
recoverFromStaleChunkError,
|
|
19
|
+
} from "@agent-native/core/client/route-chunk-recovery";
|
|
20
|
+
import {
|
|
21
|
+
DefaultSpinner,
|
|
17
22
|
ErrorReportActions,
|
|
18
23
|
getThemeInitScript,
|
|
19
24
|
} from "@agent-native/core/client/ui";
|
|
@@ -90,6 +95,7 @@ const MAIL_ERROR_COPY: Record<
|
|
|
90
95
|
fallback: string;
|
|
91
96
|
back: string;
|
|
92
97
|
reload: string;
|
|
98
|
+
loading: string;
|
|
93
99
|
sendFeedback: string;
|
|
94
100
|
feedbackPlaceholder: string;
|
|
95
101
|
openGitHubIssue: string;
|
|
@@ -100,6 +106,7 @@ const MAIL_ERROR_COPY: Record<
|
|
|
100
106
|
fallback: "Something went wrong while loading Mail.",
|
|
101
107
|
back: "Back",
|
|
102
108
|
reload: "Reload",
|
|
109
|
+
loading: "Reloading Mail...",
|
|
103
110
|
sendFeedback: "Send feedback",
|
|
104
111
|
feedbackPlaceholder:
|
|
105
112
|
"Describe what happened before this Mail error appeared.",
|
|
@@ -110,6 +117,7 @@ const MAIL_ERROR_COPY: Record<
|
|
|
110
117
|
fallback: "加载 Mail 时出现问题。",
|
|
111
118
|
back: "返回",
|
|
112
119
|
reload: "重新加载",
|
|
120
|
+
loading: "正在重新加载 Mail...",
|
|
113
121
|
sendFeedback: "发送反馈",
|
|
114
122
|
feedbackPlaceholder: "描述此 Mail 错误出现前发生了什么。",
|
|
115
123
|
openGitHubIssue: "打开 GitHub issue",
|
|
@@ -119,6 +127,7 @@ const MAIL_ERROR_COPY: Record<
|
|
|
119
127
|
fallback: "載入 Mail 時發生問題。",
|
|
120
128
|
back: "返回",
|
|
121
129
|
reload: "重新載入",
|
|
130
|
+
loading: "正在重新載入 Mail...",
|
|
122
131
|
sendFeedback: "傳送意見回饋",
|
|
123
132
|
feedbackPlaceholder: "描述此 Mail 錯誤出現前發生了什麼。",
|
|
124
133
|
openGitHubIssue: "開啟 GitHub issue",
|
|
@@ -128,6 +137,7 @@ const MAIL_ERROR_COPY: Record<
|
|
|
128
137
|
fallback: "Algo salió mal al cargar Mail.",
|
|
129
138
|
back: "Atrás",
|
|
130
139
|
reload: "Recargar",
|
|
140
|
+
loading: "Recargando Mail...",
|
|
131
141
|
sendFeedback: "Enviar comentarios",
|
|
132
142
|
feedbackPlaceholder:
|
|
133
143
|
"Describe qué pasó antes de que apareciera este error de Mail.",
|
|
@@ -138,6 +148,7 @@ const MAIL_ERROR_COPY: Record<
|
|
|
138
148
|
fallback: "Un problème est survenu lors du chargement de Mail.",
|
|
139
149
|
back: "Retour",
|
|
140
150
|
reload: "Recharger",
|
|
151
|
+
loading: "Rechargement de Mail...",
|
|
141
152
|
sendFeedback: "Envoyer un retour",
|
|
142
153
|
feedbackPlaceholder:
|
|
143
154
|
"Décrivez ce qui s'est passé avant cette erreur de Mail.",
|
|
@@ -148,6 +159,7 @@ const MAIL_ERROR_COPY: Record<
|
|
|
148
159
|
fallback: "Beim Laden von Mail ist ein Fehler aufgetreten.",
|
|
149
160
|
back: "Zurück",
|
|
150
161
|
reload: "Neu laden",
|
|
162
|
+
loading: "Mail wird neu geladen...",
|
|
151
163
|
sendFeedback: "Feedback senden",
|
|
152
164
|
feedbackPlaceholder:
|
|
153
165
|
"Beschreiben Sie, was vor diesem Mail-Fehler passiert ist.",
|
|
@@ -158,6 +170,7 @@ const MAIL_ERROR_COPY: Record<
|
|
|
158
170
|
fallback: "Mail の読み込み中に問題が発生しました。",
|
|
159
171
|
back: "戻る",
|
|
160
172
|
reload: "再読み込み",
|
|
173
|
+
loading: "Mail を再読み込み中...",
|
|
161
174
|
sendFeedback: "フィードバックを送信",
|
|
162
175
|
feedbackPlaceholder:
|
|
163
176
|
"この Mail エラーの直前に起きたことを説明してください。",
|
|
@@ -168,6 +181,7 @@ const MAIL_ERROR_COPY: Record<
|
|
|
168
181
|
fallback: "Mail을 불러오는 중 문제가 발생했습니다.",
|
|
169
182
|
back: "뒤로",
|
|
170
183
|
reload: "새로고침",
|
|
184
|
+
loading: "Mail 새로고침 중...",
|
|
171
185
|
sendFeedback: "피드백 보내기",
|
|
172
186
|
feedbackPlaceholder:
|
|
173
187
|
"이 Mail 오류가 나타나기 전에 무슨 일이 있었는지 적어 주세요.",
|
|
@@ -178,6 +192,7 @@ const MAIL_ERROR_COPY: Record<
|
|
|
178
192
|
fallback: "Algo deu errado ao carregar o Mail.",
|
|
179
193
|
back: "Voltar",
|
|
180
194
|
reload: "Recarregar",
|
|
195
|
+
loading: "Recarregando o Mail...",
|
|
181
196
|
sendFeedback: "Enviar feedback",
|
|
182
197
|
feedbackPlaceholder:
|
|
183
198
|
"Descreva o que aconteceu antes deste erro do Mail aparecer.",
|
|
@@ -188,6 +203,7 @@ const MAIL_ERROR_COPY: Record<
|
|
|
188
203
|
fallback: "Mail लोड करते समय कुछ गलत हुआ।",
|
|
189
204
|
back: "वापस",
|
|
190
205
|
reload: "रीलोड",
|
|
206
|
+
loading: "Mail रीलोड हो रहा है...",
|
|
191
207
|
sendFeedback: "फ़ीडबैक भेजें",
|
|
192
208
|
feedbackPlaceholder: "इस Mail त्रुटि से पहले क्या हुआ, उसका वर्णन करें।",
|
|
193
209
|
openGitHubIssue: "GitHub issue खोलें",
|
|
@@ -197,6 +213,7 @@ const MAIL_ERROR_COPY: Record<
|
|
|
197
213
|
fallback: "حدث خطأ أثناء تحميل Mail.",
|
|
198
214
|
back: "رجوع",
|
|
199
215
|
reload: "إعادة التحميل",
|
|
216
|
+
loading: "جارٍ إعادة تحميل Mail...",
|
|
200
217
|
sendFeedback: "إرسال الملاحظات",
|
|
201
218
|
feedbackPlaceholder: "صف ما حدث قبل ظهور خطأ Mail هذا.",
|
|
202
219
|
openGitHubIssue: "فتح مشكلة في GitHub",
|
|
@@ -469,6 +486,18 @@ export function ErrorBoundary() {
|
|
|
469
486
|
const copy =
|
|
470
487
|
MAIL_ERROR_COPY[activeErrorLocale()] ?? MAIL_ERROR_COPY[DEFAULT_LOCALE];
|
|
471
488
|
const message = routeErrorMessage(error, copy.fallback);
|
|
489
|
+
const staleChunk = isDynamicImportFailureMessage(message);
|
|
490
|
+
const [recovering, setRecovering] = useState(staleChunk);
|
|
491
|
+
|
|
492
|
+
useEffect(() => {
|
|
493
|
+
if (!staleChunk) {
|
|
494
|
+
setRecovering(false);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (!recoverFromStaleChunkError(error)) setRecovering(false);
|
|
498
|
+
}, [error, staleChunk]);
|
|
499
|
+
|
|
500
|
+
if (recovering) return <DefaultSpinner ariaLabel={copy.loading} />;
|
|
472
501
|
|
|
473
502
|
return (
|
|
474
503
|
<div className="flex min-h-screen items-center justify-center bg-background px-6 text-foreground">
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineAction } from "../../action.js";
|
|
3
|
+
import { setAgentToolApprovalPolicy } from "../tool-approval-store.js";
|
|
4
|
+
export default defineAction({
|
|
5
|
+
description: "Enable or disable future approval prompts for one action type for the signed-in user.",
|
|
6
|
+
schema: z.object({
|
|
7
|
+
toolName: z.string().trim().min(1).max(128),
|
|
8
|
+
enabled: z.boolean().default(true),
|
|
9
|
+
}),
|
|
10
|
+
http: { method: "POST" },
|
|
11
|
+
agentTool: false,
|
|
12
|
+
run: async (args, context) => {
|
|
13
|
+
const ownerEmail = context?.userEmail?.trim().toLowerCase();
|
|
14
|
+
if (!ownerEmail)
|
|
15
|
+
throw new Error("Sign in to manage approval preferences.");
|
|
16
|
+
await setAgentToolApprovalPolicy({
|
|
17
|
+
binding: {
|
|
18
|
+
ownerEmail,
|
|
19
|
+
orgId: context?.orgId?.trim() || null,
|
|
20
|
+
toolName: args.toolName,
|
|
21
|
+
},
|
|
22
|
+
enabled: args.enabled,
|
|
23
|
+
});
|
|
24
|
+
return { toolName: args.toolName, enabled: args.enabled };
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -57,4 +57,12 @@ export const AGENT_HARNESS_SESSION_MIGRATIONS = [
|
|
|
57
57
|
sqlite: "",
|
|
58
58
|
},
|
|
59
59
|
},
|
|
60
|
+
{
|
|
61
|
+
version: 4,
|
|
62
|
+
name: "agent-harness-sessions-generation",
|
|
63
|
+
sql: {
|
|
64
|
+
postgres: "ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS generation BIGINT NOT NULL DEFAULT 0",
|
|
65
|
+
sqlite: "ALTER TABLE agent_harness_sessions ADD COLUMN generation INTEGER NOT NULL DEFAULT 0",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
60
68
|
];
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { startRun } from "../run-manager.js";
|
|
2
2
|
import { registerLiveAgentHarnessSession, releaseLiveAgentHarnessSession, } from "./lifecycle.js";
|
|
3
|
-
import { getAgentHarnessSession, markAgentHarnessSessionStopped, saveAgentHarnessSession, updateAgentHarnessSession, } from "./store.js";
|
|
3
|
+
import { getAgentHarnessSession, isAgentHarnessSessionConflictError, markAgentHarnessSessionStopped, saveAgentHarnessSession, updateAgentHarnessSession, } from "./store.js";
|
|
4
4
|
import { agentHarnessEventToAgentChatEvents } from "./translate.js";
|
|
5
5
|
export function startAgentHarnessRun(opts) {
|
|
6
6
|
let harnessSession = opts.session;
|
|
7
7
|
const detachOnComplete = opts.detachOnComplete !== false;
|
|
8
|
-
return startRun(opts.runId, opts.threadId, async (send, signal) => {
|
|
8
|
+
return startRun(opts.runId, opts.threadId, async (send, signal, control) => {
|
|
9
|
+
const runControl = control ?? {
|
|
10
|
+
turnSignal: signal,
|
|
11
|
+
chunkSignal: signal,
|
|
12
|
+
chunkBoundaryReason: () => null,
|
|
13
|
+
beginChunk: () => signal,
|
|
14
|
+
};
|
|
9
15
|
let storedSessionId;
|
|
16
|
+
let harnessSessionRegistered = false;
|
|
10
17
|
let keepLiveSession = false;
|
|
11
18
|
try {
|
|
12
19
|
send({
|
|
@@ -20,7 +27,7 @@ export function startAgentHarnessRun(opts) {
|
|
|
20
27
|
runId: opts.runId,
|
|
21
28
|
ownerEmail: opts.ownerEmail ?? opts.createSession?.ownerEmail ?? null,
|
|
22
29
|
orgId: opts.orgId ?? opts.createSession?.orgId ?? null,
|
|
23
|
-
signal,
|
|
30
|
+
signal: runControl.turnSignal,
|
|
24
31
|
});
|
|
25
32
|
storedSessionId = opts.createSession?.sessionId ?? harnessSession.id;
|
|
26
33
|
await saveAgentHarnessSession({
|
|
@@ -42,50 +49,93 @@ export function startAgentHarnessRun(opts) {
|
|
|
42
49
|
ownerEmail: opts.ownerEmail ?? opts.createSession?.ownerEmail ?? null,
|
|
43
50
|
orgId: opts.orgId ?? opts.createSession?.orgId ?? null,
|
|
44
51
|
});
|
|
52
|
+
harnessSessionRegistered = true;
|
|
45
53
|
const input = {
|
|
46
54
|
...opts.input,
|
|
47
|
-
abortSignal:
|
|
55
|
+
abortSignal: runControl.chunkSignal,
|
|
48
56
|
};
|
|
57
|
+
let firstStream = true;
|
|
49
58
|
let pendingApproval = null;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
await updateAgentHarnessSession(storedSessionId, {
|
|
57
|
-
status: "idle",
|
|
58
|
-
pendingApproval: event,
|
|
59
|
+
while (true) {
|
|
60
|
+
pendingApproval = null;
|
|
61
|
+
const events = firstStream
|
|
62
|
+
? harnessSession.streamTurn(input)
|
|
63
|
+
: harnessSession.continueTurn({
|
|
64
|
+
abortSignal: runControl.chunkSignal,
|
|
59
65
|
});
|
|
66
|
+
firstStream = false;
|
|
67
|
+
try {
|
|
68
|
+
for await (const event of events) {
|
|
69
|
+
if (runControl.turnSignal.aborted)
|
|
70
|
+
break;
|
|
71
|
+
await opts.onHarnessEvent?.(event);
|
|
72
|
+
if (event.type === "approval-request") {
|
|
73
|
+
pendingApproval = event;
|
|
74
|
+
await updateAgentHarnessSession(storedSessionId, {
|
|
75
|
+
status: "idle",
|
|
76
|
+
pendingApproval: event,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (event.type === "error") {
|
|
80
|
+
throw new Error(event.error);
|
|
81
|
+
}
|
|
82
|
+
for (const chatEvent of agentHarnessEventToAgentChatEvents(event)) {
|
|
83
|
+
send(chatEvent);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (runControl.turnSignal.aborted ||
|
|
89
|
+
!runControl.chunkBoundaryReason()) {
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (runControl.turnSignal.aborted) {
|
|
94
|
+
await stopHarnessSession(harnessSession);
|
|
95
|
+
releaseLiveAgentHarnessSession(storedSessionId, harnessSession);
|
|
96
|
+
await markAgentHarnessSessionStopped(storedSessionId, "stopped");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (pendingApproval) {
|
|
100
|
+
const stored = await getAgentHarnessSession(storedSessionId);
|
|
101
|
+
const stillPending = stored?.pendingApproval &&
|
|
102
|
+
typeof stored.pendingApproval === "object" &&
|
|
103
|
+
"id" in stored.pendingApproval &&
|
|
104
|
+
stored.pendingApproval.id === pendingApproval.id;
|
|
105
|
+
if (stillPending) {
|
|
106
|
+
keepLiveSession = true;
|
|
107
|
+
await updateAgentHarnessSession(storedSessionId, {
|
|
108
|
+
status: "idle",
|
|
109
|
+
pendingApproval,
|
|
110
|
+
});
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
60
113
|
}
|
|
61
|
-
if (
|
|
62
|
-
|
|
114
|
+
if (!runControl.chunkBoundaryReason())
|
|
115
|
+
break;
|
|
116
|
+
if (!harnessSession.continueTurn) {
|
|
117
|
+
await saveHarnessCheckpoint(storedSessionId, harnessSession, opts.createSession?.resumeState, detachOnComplete);
|
|
118
|
+
if (detachOnComplete) {
|
|
119
|
+
releaseLiveAgentHarnessSession(storedSessionId, harnessSession);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
keepLiveSession = true;
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
63
125
|
}
|
|
64
|
-
|
|
65
|
-
|
|
126
|
+
if (runControl.beginChunk().aborted) {
|
|
127
|
+
await stopHarnessSession(harnessSession);
|
|
128
|
+
releaseLiveAgentHarnessSession(storedSessionId, harnessSession);
|
|
129
|
+
await markAgentHarnessSessionStopped(storedSessionId, "stopped");
|
|
130
|
+
return;
|
|
66
131
|
}
|
|
67
132
|
}
|
|
68
|
-
if (
|
|
133
|
+
if (runControl.turnSignal.aborted) {
|
|
69
134
|
await stopHarnessSession(harnessSession);
|
|
70
135
|
releaseLiveAgentHarnessSession(storedSessionId, harnessSession);
|
|
71
136
|
await markAgentHarnessSessionStopped(storedSessionId, "stopped");
|
|
72
137
|
return;
|
|
73
138
|
}
|
|
74
|
-
if (pendingApproval) {
|
|
75
|
-
const stored = await getAgentHarnessSession(storedSessionId);
|
|
76
|
-
const stillPending = stored?.pendingApproval &&
|
|
77
|
-
typeof stored.pendingApproval === "object" &&
|
|
78
|
-
"id" in stored.pendingApproval &&
|
|
79
|
-
stored.pendingApproval.id === pendingApproval.id;
|
|
80
|
-
if (stillPending) {
|
|
81
|
-
keepLiveSession = true;
|
|
82
|
-
await updateAgentHarnessSession(storedSessionId, {
|
|
83
|
-
status: "idle",
|
|
84
|
-
pendingApproval,
|
|
85
|
-
});
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
139
|
let resumeState = opts.createSession?.resumeState;
|
|
90
140
|
if (detachOnComplete && harnessSession.detach) {
|
|
91
141
|
resumeState = await harnessSession.detach();
|
|
@@ -98,6 +148,24 @@ export function startAgentHarnessRun(opts) {
|
|
|
98
148
|
});
|
|
99
149
|
}
|
|
100
150
|
catch (error) {
|
|
151
|
+
if (isAgentHarnessSessionConflictError(error)) {
|
|
152
|
+
if (!harnessSessionRegistered) {
|
|
153
|
+
await stopHarnessSession(harnessSession);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
keepLiveSession = true;
|
|
157
|
+
const latest = storedSessionId
|
|
158
|
+
? await getAgentHarnessSession(storedSessionId)
|
|
159
|
+
: null;
|
|
160
|
+
const terminal = latest?.status === "stopped" ||
|
|
161
|
+
latest?.status === "errored" ||
|
|
162
|
+
latest?.status === "destroyed";
|
|
163
|
+
keepLiveSession = !terminal;
|
|
164
|
+
if (terminal && storedSessionId) {
|
|
165
|
+
releaseLiveAgentHarnessSession(storedSessionId, harnessSession);
|
|
166
|
+
}
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
101
169
|
if (harnessSession && !keepLiveSession) {
|
|
102
170
|
await stopHarnessSession(harnessSession).catch(() => undefined);
|
|
103
171
|
}
|
|
@@ -118,6 +186,8 @@ export function startAgentHarnessRun(opts) {
|
|
|
118
186
|
}, opts.onRunComplete, {
|
|
119
187
|
...(opts.runOptions ?? {}),
|
|
120
188
|
turnId: opts.turnId ?? opts.runOptions?.turnId,
|
|
189
|
+
recoverChunkBoundaries: opts.runOptions?.recoverChunkBoundaries ?? true,
|
|
190
|
+
useHostedSoftTimeoutDefault: opts.runOptions?.useHostedSoftTimeoutDefault ?? true,
|
|
121
191
|
// A harness adapter (e.g. Claude Code, Codex) owns its own model
|
|
122
192
|
// selection internally and does not expose it here, so `model` is
|
|
123
193
|
// left for the caller to supply via `runOptions` if it knows one.
|
|
@@ -125,6 +195,17 @@ export function startAgentHarnessRun(opts) {
|
|
|
125
195
|
engineName: opts.runOptions?.engineName ?? opts.adapter.name,
|
|
126
196
|
});
|
|
127
197
|
}
|
|
198
|
+
async function saveHarnessCheckpoint(sessionId, session, resumeState, detachOnComplete) {
|
|
199
|
+
let nextResumeState = resumeState;
|
|
200
|
+
if (detachOnComplete && session.detach) {
|
|
201
|
+
nextResumeState = await session.detach();
|
|
202
|
+
}
|
|
203
|
+
await updateAgentHarnessSession(sessionId, {
|
|
204
|
+
status: "idle",
|
|
205
|
+
resumeState: nextResumeState,
|
|
206
|
+
pendingApproval: null,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
128
209
|
async function stopHarnessSession(session) {
|
|
129
210
|
if (!session)
|
|
130
211
|
return;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export type AgentHarnessSessionStatus = "running" | "idle" | "stopped" | "errored" | "destroyed";
|
|
2
|
+
export declare class AgentHarnessSessionConflictError extends Error {
|
|
3
|
+
constructor(sessionId: string);
|
|
4
|
+
}
|
|
5
|
+
export declare function isAgentHarnessSessionConflictError(error: unknown): error is AgentHarnessSessionConflictError;
|
|
2
6
|
export interface StoredAgentHarnessSession {
|
|
3
7
|
id: string;
|
|
4
8
|
harnessName: string;
|
|
@@ -12,6 +16,7 @@ export interface StoredAgentHarnessSession {
|
|
|
12
16
|
resolvedApprovalIds?: string[];
|
|
13
17
|
ownerEmail?: string | null;
|
|
14
18
|
orgId?: string | null;
|
|
19
|
+
generation: number;
|
|
15
20
|
createdAt: number;
|
|
16
21
|
updatedAt: number;
|
|
17
22
|
stoppedAt?: number | null;
|
|
@@ -30,6 +35,10 @@ export interface SaveAgentHarnessSessionInput {
|
|
|
30
35
|
ownerEmail?: string | null;
|
|
31
36
|
orgId?: string | null;
|
|
32
37
|
stoppedAt?: number | null;
|
|
38
|
+
/** Internal optimistic-concurrency token used by updateAgentHarnessSession. */
|
|
39
|
+
expectedGeneration?: number;
|
|
40
|
+
/** Snapshot used by updateAgentHarnessSession so merge and CAS share a read. */
|
|
41
|
+
existingSession?: StoredAgentHarnessSession | null;
|
|
33
42
|
}
|
|
34
43
|
export declare function ensureAgentHarnessSessionTables(): Promise<void>;
|
|
35
44
|
export declare function saveAgentHarnessSession(input: SaveAgentHarnessSessionInput): Promise<StoredAgentHarnessSession>;
|