@agent-native/core 0.172.3 → 0.172.6
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/calendar/actions/get-settings.ts +2 -5
- package/corpus/templates/calendar/actions/list-events.ts +21 -80
- package/corpus/templates/calendar/actions/update-settings.ts +11 -16
- package/corpus/templates/calendar/actions/view-screen.ts +25 -10
- package/corpus/templates/calendar/app/components/calendar/CommandPalette.tsx +7 -1
- package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +5 -2
- package/corpus/templates/calendar/app/lib/calendar-timezone.ts +3 -18
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +3 -62
- package/corpus/templates/calendar/changelog/2026-07-22-calendar-grid-local-time.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-19-the-calendar-loads-again-for-accounts-whose-saved-timezone-w.md +6 -0
- package/corpus/templates/calendar/server/handlers/settings.ts +8 -24
- package/corpus/templates/calendar/server/lib/calendar-settings.ts +58 -0
- package/corpus/templates/calendar/server/lib/find-time.ts +15 -93
- package/corpus/templates/calendar/shared/settings.ts +15 -9
- package/corpus/templates/calendar/shared/timezone.ts +127 -0
- package/corpus/templates/factory/AGENTS.md +5 -3
- package/corpus/templates/factory/actions/babysit-pull-request.ts +6 -0
- package/corpus/templates/factory/actions/list-factory-automations.ts +2 -2
- package/corpus/templates/factory/actions/save-factory-automation.ts +0 -5
- package/corpus/templates/factory/changelog/2026-08-24-slack-checking-works-for-teammate-created-factories.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-24-teammates-can-edit-and-run-factory-jobs.md +6 -0
- package/corpus/templates/factory/server/lib/factory-scope.ts +8 -0
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +1 -9
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +2 -1
- package/corpus/templates/slides/actions/list-decks.ts +79 -5
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +21 -1
- package/corpus/templates/slides/app/components/editor/ShareDialog.tsx +49 -21
- package/corpus/templates/slides/app/context/DeckContext.tsx +49 -21
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +2 -2
- package/corpus/templates/slides/app/pages/Index.tsx +11 -13
- package/corpus/templates/slides/changelog/2026-08-24-deck-previews-and-sharing-live-in-the-overflow-menu.md +5 -0
- package/dist/agent/production-agent.js +5 -1
- package/dist/agent/types.d.ts +5 -0
- package/dist/automations/service.d.ts +6 -0
- package/dist/automations/service.js +20 -0
- package/dist/client/settings/SettingsPanel.js +80 -33
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/jobs/run-now.js +2 -2
- package/dist/observability/posthog-ai.d.ts +36 -11
- package/dist/observability/posthog-ai.js +99 -30
- package/dist/observability/routes.js +9 -2
- package/dist/observability/traces.js +330 -142
- package/dist/tracking/providers.js +9 -3
- package/dist/vite/client.js +5 -2
- package/docs/content/docs-components.mdx +20 -1
- package/docs/content/observability.mdx +27 -10
- package/docs/content/template-calendar-agent.mdx +63 -26
- package/docs/content/template-calendar-developers.mdx +165 -40
- package/docs/content/template-calendar-features.mdx +276 -0
- package/docs/content/template-calendar-integrations.mdx +78 -0
- package/docs/content/template-calendar.mdx +56 -123
- package/package.json +1 -1
- package/docs/content/template-calendar-booking-links.mdx +0 -73
- package/docs/content/template-calendar-scheduling.mdx +0 -128
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
cloneElement,
|
|
8
8
|
isValidElement,
|
|
9
9
|
useCallback,
|
|
10
|
+
useEffect,
|
|
10
11
|
useState,
|
|
11
12
|
type MouseEvent,
|
|
12
13
|
type ReactElement,
|
|
@@ -22,7 +23,10 @@ import { getDeckShareLinkOrder } from "@/lib/deck-share-links";
|
|
|
22
23
|
interface ShareDialogProps {
|
|
23
24
|
deck: Deck;
|
|
24
25
|
/** Trigger element rendered as the dialog anchor (usually the Share button). */
|
|
25
|
-
children
|
|
26
|
+
children?: ReactNode;
|
|
27
|
+
/** Controlled opening for menu items that must wait for their parent to close. */
|
|
28
|
+
open?: boolean;
|
|
29
|
+
onOpenChange?: (open: boolean) => void;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
function getShareUrls(deckId: string) {
|
|
@@ -40,7 +44,12 @@ function getShareUrls(deckId: string) {
|
|
|
40
44
|
};
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
export default function ShareDialog({
|
|
47
|
+
export default function ShareDialog({
|
|
48
|
+
deck,
|
|
49
|
+
children,
|
|
50
|
+
open: requestedOpen,
|
|
51
|
+
onOpenChange,
|
|
52
|
+
}: ShareDialogProps) {
|
|
44
53
|
const t = useT();
|
|
45
54
|
const { isLocal } = useDbStatus();
|
|
46
55
|
const [open, setOpen] = useState(false);
|
|
@@ -50,6 +59,14 @@ export default function ShareDialog({ deck, children }: ShareDialogProps) {
|
|
|
50
59
|
} | null>(null);
|
|
51
60
|
const [creatingLink, setCreatingLink] = useState(false);
|
|
52
61
|
|
|
62
|
+
const setDialogOpen = useCallback(
|
|
63
|
+
(nextOpen: boolean) => {
|
|
64
|
+
setOpen(nextOpen);
|
|
65
|
+
onOpenChange?.(nextOpen);
|
|
66
|
+
},
|
|
67
|
+
[onOpenChange],
|
|
68
|
+
);
|
|
69
|
+
|
|
53
70
|
const shareUrls = getShareUrls(deck.id);
|
|
54
71
|
const shareLinkOrder = getDeckShareLinkOrder(deck.visibility);
|
|
55
72
|
const shareToken =
|
|
@@ -61,11 +78,11 @@ export default function ShareDialog({ deck, children }: ShareDialogProps) {
|
|
|
61
78
|
|
|
62
79
|
const openShareDialog = useCallback(async () => {
|
|
63
80
|
if (isLocal) {
|
|
64
|
-
|
|
81
|
+
setDialogOpen(true);
|
|
65
82
|
return;
|
|
66
83
|
}
|
|
67
84
|
if (shareToken) {
|
|
68
|
-
|
|
85
|
+
setDialogOpen(true);
|
|
69
86
|
return;
|
|
70
87
|
}
|
|
71
88
|
if (creatingLink) return;
|
|
@@ -97,29 +114,40 @@ export default function ShareDialog({ deck, children }: ShareDialogProps) {
|
|
|
97
114
|
throw new Error(t("share.createFailed"));
|
|
98
115
|
}
|
|
99
116
|
setShareLink({ deckId: deck.id, token: payload.shareToken });
|
|
100
|
-
|
|
117
|
+
setDialogOpen(true);
|
|
101
118
|
} catch (error) {
|
|
102
119
|
toast.error(
|
|
103
120
|
error instanceof Error ? error.message : t("share.createFailed"),
|
|
104
121
|
);
|
|
122
|
+
if (requestedOpen !== undefined) setDialogOpen(false);
|
|
105
123
|
} finally {
|
|
106
124
|
setCreatingLink(false);
|
|
107
125
|
}
|
|
108
|
-
}, [creatingLink, deck, isLocal, shareToken, t]);
|
|
126
|
+
}, [creatingLink, deck, isLocal, setDialogOpen, shareToken, t]);
|
|
127
|
+
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (requestedOpen === undefined) return;
|
|
130
|
+
if (!requestedOpen) {
|
|
131
|
+
setOpen(false);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (!open) void openShareDialog();
|
|
135
|
+
}, [open, openShareDialog, requestedOpen]);
|
|
109
136
|
|
|
110
|
-
const trigger =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
137
|
+
const trigger =
|
|
138
|
+
children && isValidElement(children)
|
|
139
|
+
? (() => {
|
|
140
|
+
const triggerElement = children as ReactElement<{
|
|
141
|
+
onClick?: (event: MouseEvent) => void;
|
|
142
|
+
}>;
|
|
143
|
+
return cloneElement(triggerElement, {
|
|
144
|
+
onClick: (event) => {
|
|
145
|
+
triggerElement.props.onClick?.(event);
|
|
146
|
+
if (!event.defaultPrevented) void openShareDialog();
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
})()
|
|
150
|
+
: children;
|
|
123
151
|
|
|
124
152
|
return (
|
|
125
153
|
<>
|
|
@@ -128,12 +156,12 @@ export default function ShareDialog({ deck, children }: ShareDialogProps) {
|
|
|
128
156
|
<CloudUpgrade
|
|
129
157
|
title={t("share.title")}
|
|
130
158
|
description={t("share.cloudUpgradeDescription")}
|
|
131
|
-
onClose={() =>
|
|
159
|
+
onClose={() => setDialogOpen(false)}
|
|
132
160
|
/>
|
|
133
161
|
) : null}
|
|
134
162
|
<CoreShareDialog
|
|
135
163
|
open={open && !isLocal}
|
|
136
|
-
onClose={() =>
|
|
164
|
+
onClose={() => setDialogOpen(false)}
|
|
137
165
|
resourceType="deck"
|
|
138
166
|
resourceId={deck.id}
|
|
139
167
|
resourceTitle={deck.title}
|
|
@@ -147,6 +147,8 @@ export interface Deck {
|
|
|
147
147
|
starred?: boolean;
|
|
148
148
|
/** Slide aspect ratio (defaults to 16:9 when absent for backwards compat) */
|
|
149
149
|
aspectRatio?: AspectRatio;
|
|
150
|
+
/** First slide returned by the light deck listing for home-page previews. */
|
|
151
|
+
previewSlide?: Slide;
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
export type DeckPersistenceResult =
|
|
@@ -199,19 +201,18 @@ interface DeckContextType {
|
|
|
199
201
|
) => Deck;
|
|
200
202
|
ensureDeckPersisted: (id: string) => Promise<DeckPersistenceResult>;
|
|
201
203
|
/**
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* server, then fires the duplicate-deck action in the background. On error,
|
|
205
|
-
* the optimistic deck is rolled back.
|
|
204
|
+
* Duplicate a deck, hydrating a preview-only source before creating the
|
|
205
|
+
* optimistic copy. On error, the optimistic deck is rolled back.
|
|
206
206
|
*
|
|
207
|
-
* Returns the optimistic deck (or `null` if the source deck isn't found
|
|
207
|
+
* Returns the optimistic deck (or `null` if the source deck isn't found or
|
|
208
|
+
* could not be hydrated).
|
|
208
209
|
*/
|
|
209
210
|
duplicateDeck: (
|
|
210
211
|
sourceDeckId: string,
|
|
211
212
|
newId: string,
|
|
212
213
|
title?: string,
|
|
213
214
|
onFailure?: () => void,
|
|
214
|
-
) => Deck | null
|
|
215
|
+
) => Promise<Deck | null>;
|
|
215
216
|
deleteDeck: (id: string) => void;
|
|
216
217
|
updateDeck: (
|
|
217
218
|
id: string,
|
|
@@ -325,6 +326,8 @@ function normalizeActionDeck(value: unknown): Deck | null {
|
|
|
325
326
|
const deckRecord = deck as unknown as Record<string, unknown>;
|
|
326
327
|
const cleanedDeck = { ...deckRecord };
|
|
327
328
|
for (const field of GET_DECK_ONLY_DECK_FIELDS) delete cleanedDeck[field];
|
|
329
|
+
const previewSlide = deckRecord.previewSlide;
|
|
330
|
+
delete cleanedDeck.previewSlide;
|
|
328
331
|
|
|
329
332
|
// Strip the same decorative fields from every slide, so a deck fetched from
|
|
330
333
|
// `get-deck` is structurally identical to one built by local mutations —
|
|
@@ -356,9 +359,20 @@ function normalizeActionDeck(value: unknown): Deck | null {
|
|
|
356
359
|
? deck.updatedAt
|
|
357
360
|
: deck.createdAt || "",
|
|
358
361
|
slides,
|
|
362
|
+
...(previewSlide && typeof previewSlide === "object"
|
|
363
|
+
? { previewSlide: previewSlide as Slide }
|
|
364
|
+
: {}),
|
|
359
365
|
} as Deck;
|
|
360
366
|
}
|
|
361
367
|
|
|
368
|
+
export function getDuplicateSourceSlides(deck: Deck): Slide[] {
|
|
369
|
+
return deck.slides.length > 0
|
|
370
|
+
? deck.slides
|
|
371
|
+
: deck.previewSlide
|
|
372
|
+
? [deck.previewSlide]
|
|
373
|
+
: [];
|
|
374
|
+
}
|
|
375
|
+
|
|
362
376
|
// Debounced save to API + save-state listeners (so the toolbar indicator
|
|
363
377
|
// can show "Saving…" / "Saved"). The map tracks pending debounce timers;
|
|
364
378
|
// `inFlight` tracks active fetches. Combined, they answer "is anything
|
|
@@ -1040,7 +1054,7 @@ async function fetchDecksFromAPI(): Promise<Deck[] | null> {
|
|
|
1040
1054
|
try {
|
|
1041
1055
|
const result = await callAction<DeckListActionResult>(
|
|
1042
1056
|
"list-decks",
|
|
1043
|
-
{ light: "true" },
|
|
1057
|
+
{ light: "true", includePreview: "true" },
|
|
1044
1058
|
{ method: "GET" },
|
|
1045
1059
|
);
|
|
1046
1060
|
if (!Array.isArray(result?.decks)) {
|
|
@@ -1172,7 +1186,7 @@ async function fetchDecksForCurrentRoute(): Promise<Deck[] | null> {
|
|
|
1172
1186
|
}
|
|
1173
1187
|
if (!currentOpenDeckId) return loaded;
|
|
1174
1188
|
|
|
1175
|
-
// The list
|
|
1189
|
+
// The list has only first-slide previews. Hydrate just the deck the user
|
|
1176
1190
|
// opened so the editor gets full slide content without making the home page
|
|
1177
1191
|
// download every deck body.
|
|
1178
1192
|
const directDeck = await fetchDeckFromAPI(currentOpenDeckId);
|
|
@@ -2143,15 +2157,27 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
2143
2157
|
);
|
|
2144
2158
|
|
|
2145
2159
|
const duplicateDeck = useCallback(
|
|
2146
|
-
(
|
|
2160
|
+
async (
|
|
2147
2161
|
sourceDeckId: string,
|
|
2148
2162
|
newId: string,
|
|
2149
2163
|
title?: string,
|
|
2150
2164
|
onFailure?: () => void,
|
|
2151
|
-
): Deck | null => {
|
|
2165
|
+
): Promise<Deck | null> => {
|
|
2152
2166
|
if (pendingDuplicateSourceIdsRef.current.has(sourceDeckId)) return null;
|
|
2153
|
-
|
|
2154
|
-
|
|
2167
|
+
pendingDuplicateSourceIdsRef.current.add(sourceDeckId);
|
|
2168
|
+
let source = decks.find((d) => d.id === sourceDeckId);
|
|
2169
|
+
if (!source) {
|
|
2170
|
+
pendingDuplicateSourceIdsRef.current.delete(sourceDeckId);
|
|
2171
|
+
return null;
|
|
2172
|
+
}
|
|
2173
|
+
if (source.slides.length === 0 && source.previewSlide) {
|
|
2174
|
+
const hydrated = await fetchDeckFromAPI(sourceDeckId);
|
|
2175
|
+
if (!hydrated) {
|
|
2176
|
+
pendingDuplicateSourceIdsRef.current.delete(sourceDeckId);
|
|
2177
|
+
return null;
|
|
2178
|
+
}
|
|
2179
|
+
source = hydrated;
|
|
2180
|
+
}
|
|
2155
2181
|
|
|
2156
2182
|
const now = new Date().toISOString();
|
|
2157
2183
|
const newTitle = title || `Copy of ${source.title}`;
|
|
@@ -2172,7 +2198,8 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
2172
2198
|
createdByMe: true,
|
|
2173
2199
|
shareToken: undefined,
|
|
2174
2200
|
};
|
|
2175
|
-
|
|
2201
|
+
delete optimistic.previewSlide;
|
|
2202
|
+
optimistic.slides = getDuplicateSourceSlides(source).map((s) => ({
|
|
2176
2203
|
...s,
|
|
2177
2204
|
id: `slide-${nanoid(8)}`,
|
|
2178
2205
|
}));
|
|
@@ -2181,7 +2208,6 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
2181
2208
|
// the duplicate-deck action's INSERT lands.
|
|
2182
2209
|
pendingCreateIdsRef.current.add(newId);
|
|
2183
2210
|
noteLocalCreate(newId);
|
|
2184
|
-
pendingDuplicateSourceIdsRef.current.add(sourceDeckId);
|
|
2185
2211
|
|
|
2186
2212
|
// Fire the action in the background. On error, roll back.
|
|
2187
2213
|
const duplicatePromise = callAction<DuplicateDeckActionResult>(
|
|
@@ -2190,10 +2216,12 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
2190
2216
|
deckId: sourceDeckId,
|
|
2191
2217
|
newId,
|
|
2192
2218
|
title,
|
|
2193
|
-
//
|
|
2194
|
-
//
|
|
2195
|
-
//
|
|
2196
|
-
|
|
2219
|
+
// Preview-only sources were hydrated above. An empty array is not
|
|
2220
|
+
// a valid optimistic slide-id projection, so omit it for empty
|
|
2221
|
+
// decks and let the server generate ids for any uncovered slides.
|
|
2222
|
+
...(optimistic.slides.length > 0
|
|
2223
|
+
? { slideIds: optimistic.slides.map((s) => s.id) }
|
|
2224
|
+
: {}),
|
|
2197
2225
|
},
|
|
2198
2226
|
).then(() => undefined);
|
|
2199
2227
|
pendingCreatePromisesRef.current.set(newId, duplicatePromise);
|
|
@@ -2213,9 +2241,9 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
2213
2241
|
}
|
|
2214
2242
|
console.error("Duplicate failed:", err);
|
|
2215
2243
|
// Roll back: drop the optimistic deck from local state. The caller
|
|
2216
|
-
// (via onFailure) is responsible for navigating away if the user
|
|
2217
|
-
// still sitting on this now-gone deck's route — otherwise
|
|
2218
|
-
// stranded on a "Deck unavailable" screen with no way back.
|
|
2244
|
+
// (via onFailure) is responsible for navigating away if the user
|
|
2245
|
+
// is still sitting on this now-gone deck's route — otherwise
|
|
2246
|
+
// they're stranded on a "Deck unavailable" screen with no way back.
|
|
2219
2247
|
setDecks((prev) => prev.filter((d) => d.id !== newId));
|
|
2220
2248
|
onFailure?.();
|
|
2221
2249
|
})
|
|
@@ -1545,9 +1545,9 @@ export default function DeckEditor() {
|
|
|
1545
1545
|
onTogglePinMode={togglePinMode}
|
|
1546
1546
|
textBoxMode={textBoxMode}
|
|
1547
1547
|
onToggleTextBoxMode={toggleTextBoxMode}
|
|
1548
|
-
onDuplicateDeck={() => {
|
|
1548
|
+
onDuplicateDeck={async () => {
|
|
1549
1549
|
const newId = `deck-${nanoid()}`;
|
|
1550
|
-
const optimistic = duplicateDeck(id, newId, undefined, () => {
|
|
1550
|
+
const optimistic = await duplicateDeck(id, newId, undefined, () => {
|
|
1551
1551
|
// The background duplicate-deck action failed after we already
|
|
1552
1552
|
// navigated to the optimistic copy. If the user is still there,
|
|
1553
1553
|
// send them back instead of stranding them on a "Deck
|
|
@@ -1289,19 +1289,17 @@ export default function Index() {
|
|
|
1289
1289
|
// first (the same path the editor's own Duplicate uses) and navigate to
|
|
1290
1290
|
// that; the background action reconciles or rolls the copy back.
|
|
1291
1291
|
const handleDuplicate = useCallback(
|
|
1292
|
-
(id: string) => {
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
toast.error(t("home.duplicateFailed"));
|
|
1304
|
-
});
|
|
1292
|
+
async (id: string) => {
|
|
1293
|
+
const newId = `deck-${nanoid()}`;
|
|
1294
|
+
const copy = await duplicateDeck(id, newId, undefined, () => {
|
|
1295
|
+
// The background duplicate-deck action failed after we already
|
|
1296
|
+
// navigated to the optimistic copy's route. If the user is still
|
|
1297
|
+
// there, send them back to the deck list instead of stranding them
|
|
1298
|
+
// on a "Deck unavailable" screen for a deck that no longer exists.
|
|
1299
|
+
if (deckIdFromPathname(window.location.pathname) === newId) {
|
|
1300
|
+
navigate("/");
|
|
1301
|
+
}
|
|
1302
|
+
toast.error(t("home.duplicateFailed"));
|
|
1305
1303
|
});
|
|
1306
1304
|
// The context refuses a second copy of the same deck while the first
|
|
1307
1305
|
// one's action is still in flight.
|
|
@@ -3528,7 +3528,11 @@ export async function runAgentLoop(opts) {
|
|
|
3528
3528
|
if (!modelStreamBracketOpen)
|
|
3529
3529
|
return;
|
|
3530
3530
|
modelStreamBracketOpen = false;
|
|
3531
|
-
send({
|
|
3531
|
+
send({
|
|
3532
|
+
type: "model_stream",
|
|
3533
|
+
status: "end",
|
|
3534
|
+
...(terminalStopReason ? { reason: terminalStopReason } : {}),
|
|
3535
|
+
});
|
|
3532
3536
|
};
|
|
3533
3537
|
let lastModelStreamProgressAt = Date.now();
|
|
3534
3538
|
// FIX 2: true once a real (non-heartbeat) engine-stream event has been
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -305,6 +305,11 @@ export type AgentChatEvent = {
|
|
|
305
305
|
*/
|
|
306
306
|
type: "model_stream";
|
|
307
307
|
status: "start" | "end";
|
|
308
|
+
/** Why the model stopped, on the closing event: `end_turn`, `tool_use`,
|
|
309
|
+
* `max_tokens`, `stop_sequence`, `error`. Absent when the stream was cut
|
|
310
|
+
* before the engine reported one — a truncated call and a call that
|
|
311
|
+
* ended cleanly must stay distinguishable. */
|
|
312
|
+
reason?: "end_turn" | "tool_use" | "max_tokens" | "stop_sequence" | "error";
|
|
308
313
|
} | {
|
|
309
314
|
type: "tool_start";
|
|
310
315
|
tool: string;
|
|
@@ -67,6 +67,12 @@ export interface UpdateAutomationInput {
|
|
|
67
67
|
* automation classifier.
|
|
68
68
|
*/
|
|
69
69
|
export declare function canUpdateAutomationResource(actorInput: AutomationActor, resource: Resource): Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* Factory is a shared team workspace: any current org member may queue Run now
|
|
72
|
+
* for that app's Factory-domain org jobs. Mail/CRM and other automations stay
|
|
73
|
+
* on creator-or-admin `canUpdate`.
|
|
74
|
+
*/
|
|
75
|
+
export declare function canQueueAutomationRunNow(actorInput: AutomationActor, resource: Resource, scope: AutomationScope): Promise<boolean>;
|
|
70
76
|
export declare function listAutomationDefinitions(actorInput: AutomationActor, scope: AutomationScope): Promise<AutomationDefinition[]>;
|
|
71
77
|
export declare function defineAutomation(actorInput: AutomationActor, input: DefineAutomationInput): Promise<DefinedAutomation>;
|
|
72
78
|
export declare function updateAutomation(actorInput: AutomationActor, input: UpdateAutomationInput): Promise<AutomationDefinition>;
|
|
@@ -108,6 +108,26 @@ export async function canUpdateAutomationResource(actorInput, resource) {
|
|
|
108
108
|
return false;
|
|
109
109
|
return (await mutationAccess(actor, resource, meta)).canUpdate;
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Factory is a shared team workspace: any current org member may queue Run now
|
|
113
|
+
* for that app's Factory-domain org jobs. Mail/CRM and other automations stay
|
|
114
|
+
* on creator-or-admin `canUpdate`.
|
|
115
|
+
*/
|
|
116
|
+
export async function canQueueAutomationRunNow(actorInput, resource, scope) {
|
|
117
|
+
const { meta } = parseJobResource(resource.content);
|
|
118
|
+
const actor = normalizeActor(actorInput);
|
|
119
|
+
if (!jobBelongsToApp(meta, actor.appId))
|
|
120
|
+
return false;
|
|
121
|
+
const access = await mutationAccess(actor, resource, meta);
|
|
122
|
+
if (access.canUpdate)
|
|
123
|
+
return true;
|
|
124
|
+
const resourceOrgId = organizationIdFromResourceOwner(resource.owner);
|
|
125
|
+
return (scope === "organization" &&
|
|
126
|
+
Boolean(resourceOrgId) &&
|
|
127
|
+
actor.orgId === resourceOrgId &&
|
|
128
|
+
actor.appId === "factory" &&
|
|
129
|
+
meta.domain === "factory");
|
|
130
|
+
}
|
|
111
131
|
function assertExplicitAutomation(resource) {
|
|
112
132
|
const parsed = parseJobResource(resource.content);
|
|
113
133
|
if (parsed.classification.kind !== "automation") {
|