@agent-native/core 0.168.7 → 0.168.9
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/actions/mutate-dashboard.ts +8 -1
- package/corpus/templates/calendar/actions/delete-event.ts +34 -18
- package/corpus/templates/calendar/actions/delete-events.ts +13 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +22 -3
- package/corpus/templates/calendar/app/lib/calendar-timezone.ts +19 -0
- package/corpus/templates/calendar/server/lib/google-api.ts +18 -1
- package/corpus/templates/clips/actions/finalize-meeting.ts +16 -0
- package/corpus/templates/clips/actions/list-meetings.ts +1 -0
- package/corpus/templates/clips/actions/sync-calendars.ts +10 -29
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +28 -12
- package/corpus/templates/clips/desktop/src/lib/meeting-stop.ts +16 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +19 -9
- package/corpus/templates/factory/.agents/skills/factory-graphs/SKILL.md +5 -0
- package/corpus/templates/factory/AGENTS.md +12 -22
- package/corpus/templates/factory/README.md +11 -8
- package/corpus/templates/factory/actions/approve-factory-item.ts +35 -8
- package/corpus/templates/factory/actions/babysit-agent-native-pull-request.ts +24 -9
- package/corpus/templates/factory/actions/create-factory.ts +211 -0
- package/corpus/templates/factory/actions/evaluate-triage-item.ts +29 -5
- package/corpus/templates/factory/actions/get-factory-graph.ts +1 -1
- package/corpus/templates/factory/actions/get-slack-feedback-context.ts +20 -11
- package/corpus/templates/factory/actions/get-triage-config.ts +13 -12
- package/corpus/templates/factory/actions/get-triage-item.ts +28 -6
- package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +33 -17
- package/corpus/templates/factory/actions/ingest-github-observation.ts +10 -4
- package/corpus/templates/factory/actions/list-factory-audit.ts +11 -4
- package/corpus/templates/factory/actions/list-factory-automations.ts +34 -12
- package/corpus/templates/factory/actions/list-triage-items.ts +11 -3
- package/corpus/templates/factory/actions/list-triage-rules.ts +11 -4
- package/corpus/templates/factory/actions/poll-github-sources.ts +24 -10
- package/corpus/templates/factory/actions/poll-sentry-errors.ts +26 -11
- package/corpus/templates/factory/actions/poll-slack-channel.ts +31 -13
- package/corpus/templates/factory/actions/reconcile-triage-run.ts +59 -9
- package/corpus/templates/factory/actions/record-triage-feedback.ts +14 -3
- package/corpus/templates/factory/actions/run-factory-automation.ts +12 -2
- package/corpus/templates/factory/actions/save-factory-automation.ts +82 -21
- package/corpus/templates/factory/actions/save-factory-graph.ts +10 -1
- package/corpus/templates/factory/actions/save-triage-config.ts +90 -36
- package/corpus/templates/factory/actions/save-triage-rule.ts +16 -3
- package/corpus/templates/factory/actions/start-builder-for-item.ts +28 -20
- package/corpus/templates/factory/actions/suggest-factory-rules.ts +11 -4
- package/corpus/templates/factory/actions/view-screen.ts +22 -30
- package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +7 -2
- package/corpus/templates/factory/app/components/factory/FactoryWorkspaceActions.tsx +7 -22
- package/corpus/templates/factory/app/components/factory/NewFactoryForm.tsx +260 -0
- package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +2 -1
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +3 -3
- package/corpus/templates/factory/app/i18n/en-US.ts +18 -1
- package/corpus/templates/factory/app/routes/factory.tsx +78 -62
- package/corpus/templates/factory/app/routes/{factory-settings.tsx → new-factory.tsx} +12 -9
- package/corpus/templates/factory/server/db/schema.ts +18 -3
- package/corpus/templates/factory/server/factory-graph/contracts.ts +24 -0
- package/corpus/templates/factory/server/factory-graph/store.ts +16 -9
- package/corpus/templates/factory/server/lib/factory-automation-plan.ts +136 -0
- package/corpus/templates/factory/server/lib/factory-automation-repair.ts +31 -0
- package/corpus/templates/factory/server/lib/factory-scope.ts +416 -0
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +11 -0
- package/corpus/templates/factory/server/plugins/agent-chat.ts +4 -2
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +80 -0
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +142 -43
- package/corpus/templates/factory/server/triage/audit.ts +4 -0
- package/corpus/templates/factory/server/triage/ids.ts +4 -0
- package/dist/cli/native-dependencies.js +4 -1
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.js +9 -4
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat/index.js +1 -1
- package/dist/client/chat-first/apps-rail.js +6 -6
- package/dist/client/chat-first/primary-nav.js +14 -6
- package/dist/client/integrations/IntegrationsPanel.d.ts +15 -0
- package/dist/client/integrations/IntegrationsPanel.js +29 -21
- package/dist/client/integrations/index.d.ts +1 -1
- package/dist/client/integrations/index.js +1 -1
- package/dist/client/resources/McpIntegrationDialog.js +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
|
@@ -49,6 +49,13 @@ const mutationTargetSchema = {
|
|
|
49
49
|
.describe("Where in rowNumber to place the panel. Defaults to end."),
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
const insertPanelSchema = z
|
|
53
|
+
.record(z.string(), z.unknown())
|
|
54
|
+
.refine(
|
|
55
|
+
(panel) => typeof panel.id === "string" && panel.id.trim().length > 0,
|
|
56
|
+
{ message: "panel.id must be a non-empty string" },
|
|
57
|
+
);
|
|
58
|
+
|
|
52
59
|
const mutationOperationSchema = z.discriminatedUnion("op", [
|
|
53
60
|
z.object({
|
|
54
61
|
op: z.literal("movePanels"),
|
|
@@ -72,7 +79,7 @@ const mutationOperationSchema = z.discriminatedUnion("op", [
|
|
|
72
79
|
}),
|
|
73
80
|
z.object({
|
|
74
81
|
op: z.literal("insertPanel"),
|
|
75
|
-
panel:
|
|
82
|
+
panel: insertPanelSchema,
|
|
76
83
|
...mutationTargetSchema,
|
|
77
84
|
}),
|
|
78
85
|
z.object({
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
normalizeGuestNotificationMessage,
|
|
6
6
|
sendEventGuestNotificationNote,
|
|
7
7
|
} from "../server/lib/event-guest-notifications.js";
|
|
8
|
+
import { isGoogleNotFoundError } from "../server/lib/google-api.js";
|
|
8
9
|
import * as googleCalendar from "../server/lib/google-calendar.js";
|
|
9
10
|
import {
|
|
10
11
|
cliBoolean,
|
|
@@ -71,25 +72,39 @@ export default defineAction({
|
|
|
71
72
|
? "none"
|
|
72
73
|
: (args.sendUpdates ?? (shouldNotifyGuests ? "all" : "none")),
|
|
73
74
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
let eventForNotification;
|
|
76
|
+
try {
|
|
77
|
+
eventForNotification = shouldNotifyGuests
|
|
78
|
+
? await googleCalendar.getEvent(googleEventId, {
|
|
79
|
+
ownerEmail,
|
|
80
|
+
accountEmail,
|
|
81
|
+
})
|
|
82
|
+
: undefined;
|
|
80
83
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
84
|
+
if (args.removeOnly) {
|
|
85
|
+
await googleCalendar.removeEventFromCalendar(
|
|
86
|
+
googleEventId,
|
|
87
|
+
{ ownerEmail, accountEmail },
|
|
88
|
+
options,
|
|
89
|
+
);
|
|
90
|
+
} else {
|
|
91
|
+
await googleCalendar.deleteEvent(
|
|
92
|
+
googleEventId,
|
|
93
|
+
{ ownerEmail, accountEmail },
|
|
94
|
+
options,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (!isGoogleNotFoundError(error)) throw error;
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
success: true,
|
|
102
|
+
alreadyAbsent: true,
|
|
103
|
+
id: `google-${googleEventId}`,
|
|
104
|
+
accountEmail,
|
|
105
|
+
scope: args.scope,
|
|
106
|
+
removedOnly: args.removeOnly ?? false,
|
|
107
|
+
};
|
|
93
108
|
}
|
|
94
109
|
|
|
95
110
|
const guestNotification =
|
|
@@ -105,6 +120,7 @@ export default defineAction({
|
|
|
105
120
|
|
|
106
121
|
return {
|
|
107
122
|
success: true,
|
|
123
|
+
alreadyAbsent: false,
|
|
108
124
|
id: `google-${googleEventId}`,
|
|
109
125
|
accountEmail,
|
|
110
126
|
scope: args.scope,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type WeekdayName,
|
|
11
11
|
} from "../server/lib/event-weekday.js";
|
|
12
12
|
import { zonedDateTimeToUtcIso } from "../server/lib/find-time.js";
|
|
13
|
+
import { isGoogleNotFoundError } from "../server/lib/google-api.js";
|
|
13
14
|
import * as googleCalendar from "../server/lib/google-calendar.js";
|
|
14
15
|
import type { CalendarEvent } from "../shared/api.js";
|
|
15
16
|
import {
|
|
@@ -36,7 +37,7 @@ const MAX_MATCHED_EVENTS = 200;
|
|
|
36
37
|
* firing every delete at once and turning a clean batch into retries. */
|
|
37
38
|
const DELETE_CONCURRENCY = 4;
|
|
38
39
|
|
|
39
|
-
type Outcome = "deleted" | "matched" | "skipped" | "failed";
|
|
40
|
+
type Outcome = "deleted" | "already_absent" | "matched" | "skipped" | "failed";
|
|
40
41
|
|
|
41
42
|
interface EventResult {
|
|
42
43
|
id: string;
|
|
@@ -449,6 +450,7 @@ export default defineAction({
|
|
|
449
450
|
...summaryBase,
|
|
450
451
|
dryRun: true,
|
|
451
452
|
deleted: 0,
|
|
453
|
+
alreadyAbsent: 0,
|
|
452
454
|
failed: 0,
|
|
453
455
|
skipped: results.length,
|
|
454
456
|
events: [...targets.map((target) => target.display), ...results],
|
|
@@ -483,6 +485,13 @@ export default defineAction({
|
|
|
483
485
|
}
|
|
484
486
|
return { ...target.display, outcome: "deleted" };
|
|
485
487
|
} catch (error) {
|
|
488
|
+
if (isGoogleNotFoundError(error)) {
|
|
489
|
+
return {
|
|
490
|
+
...target.display,
|
|
491
|
+
outcome: "already_absent",
|
|
492
|
+
reason: "Already absent from Google Calendar",
|
|
493
|
+
};
|
|
494
|
+
}
|
|
486
495
|
return {
|
|
487
496
|
...target.display,
|
|
488
497
|
outcome: "failed",
|
|
@@ -498,6 +507,9 @@ export default defineAction({
|
|
|
498
507
|
dryRun: false,
|
|
499
508
|
deleted: deleteResults.filter((entry) => entry.outcome === "deleted")
|
|
500
509
|
.length,
|
|
510
|
+
alreadyAbsent: deleteResults.filter(
|
|
511
|
+
(entry) => entry.outcome === "already_absent",
|
|
512
|
+
).length,
|
|
501
513
|
failed: deleteResults.filter((entry) => entry.outcome === "failed")
|
|
502
514
|
.length,
|
|
503
515
|
skipped: results.length,
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
} from "@/components/ui/tooltip";
|
|
36
36
|
import { useUpdateEvent } from "@/hooks/use-events";
|
|
37
37
|
import { useViewPreferences } from "@/hooks/use-view-preferences";
|
|
38
|
+
import { getDisplayDateInTimezone } from "@/lib/calendar-timezone";
|
|
38
39
|
import { getEditableEventTitle } from "@/lib/event-form-utils";
|
|
39
40
|
import { isOutOfOfficeEvent } from "@/lib/out-of-office";
|
|
40
41
|
import { cn } from "@/lib/utils";
|
|
@@ -447,15 +448,33 @@ export function EventDetailPanel({
|
|
|
447
448
|
) : (
|
|
448
449
|
<>
|
|
449
450
|
<span className="text-foreground">
|
|
450
|
-
{format(
|
|
451
|
+
{format(
|
|
452
|
+
getDisplayDateInTimezone(
|
|
453
|
+
event.start,
|
|
454
|
+
event.startTimeZone ?? event.endTimeZone,
|
|
455
|
+
),
|
|
456
|
+
"h:mm a",
|
|
457
|
+
)}
|
|
451
458
|
{" → "}
|
|
452
|
-
{format(
|
|
459
|
+
{format(
|
|
460
|
+
getDisplayDateInTimezone(
|
|
461
|
+
event.end,
|
|
462
|
+
event.endTimeZone ?? event.startTimeZone,
|
|
463
|
+
),
|
|
464
|
+
"h:mm a",
|
|
465
|
+
)}
|
|
453
466
|
</span>
|
|
454
467
|
<span className="ml-2 text-muted-foreground/70">
|
|
455
468
|
{formatDuration(event.start, event.end)}
|
|
456
469
|
</span>
|
|
457
470
|
<div className="mt-0.5 text-muted-foreground">
|
|
458
|
-
{format(
|
|
471
|
+
{format(
|
|
472
|
+
getDisplayDateInTimezone(
|
|
473
|
+
event.start,
|
|
474
|
+
event.startTimeZone ?? event.endTimeZone,
|
|
475
|
+
),
|
|
476
|
+
"EEE MMM d",
|
|
477
|
+
)}
|
|
459
478
|
</div>
|
|
460
479
|
</>
|
|
461
480
|
)}
|
|
@@ -118,6 +118,25 @@ export function getDateTimePartsInTimezone(
|
|
|
118
118
|
return dateTimeParts(value, normalizeTimezone(timezone));
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Build a local Date carrying an event's wall-clock fields so date-fns can
|
|
123
|
+
* format those fields without converting them through the browser timezone.
|
|
124
|
+
* The result is for display only; it must not be used for date arithmetic.
|
|
125
|
+
*/
|
|
126
|
+
export function getDisplayDateInTimezone(
|
|
127
|
+
value: Date | string,
|
|
128
|
+
timezone?: string | null,
|
|
129
|
+
): Date {
|
|
130
|
+
const parsed = value instanceof Date ? value : new Date(value);
|
|
131
|
+
if (!timezone) return parsed;
|
|
132
|
+
|
|
133
|
+
const parts = getDateTimePartsInTimezone(value, timezone);
|
|
134
|
+
if (!parts) return parsed;
|
|
135
|
+
|
|
136
|
+
const [year, month, day] = parts.date.split("-").map(Number);
|
|
137
|
+
return new Date(year, month - 1, day, parts.hour, parts.minute, parts.second);
|
|
138
|
+
}
|
|
139
|
+
|
|
121
140
|
export function getDateKeyInTimezone(
|
|
122
141
|
value: Date | string,
|
|
123
142
|
timezone: string,
|
|
@@ -113,6 +113,23 @@ export function createOAuth2Client(
|
|
|
113
113
|
// Authenticated fetch helper
|
|
114
114
|
// ---------------------------------------------------------------------------
|
|
115
115
|
|
|
116
|
+
export class GoogleApiError extends Error {
|
|
117
|
+
readonly status: number;
|
|
118
|
+
|
|
119
|
+
constructor(status: number, message: string) {
|
|
120
|
+
super(`Google API error (${status}): ${message}`);
|
|
121
|
+
this.name = "GoogleApiError";
|
|
122
|
+
this.status = status;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function isGoogleNotFoundError(error: unknown): boolean {
|
|
127
|
+
return (
|
|
128
|
+
(error instanceof GoogleApiError && error.status === 404) ||
|
|
129
|
+
(error instanceof Error && /^Google API error \(404\):/.test(error.message))
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
116
133
|
export async function googleFetch(
|
|
117
134
|
url: string,
|
|
118
135
|
accessToken: string,
|
|
@@ -133,7 +150,7 @@ export async function googleFetch(
|
|
|
133
150
|
(data as any)?.error?.message ||
|
|
134
151
|
(data as any)?.error_description ||
|
|
135
152
|
res.statusText;
|
|
136
|
-
throw new
|
|
153
|
+
throw new GoogleApiError(res.status, msg);
|
|
137
154
|
}
|
|
138
155
|
|
|
139
156
|
return data;
|
|
@@ -27,6 +27,17 @@ import { loadAgentsMdContext } from "./lib/agents-md-context.js";
|
|
|
27
27
|
// handles crashed processes without stealing an active text-model run.
|
|
28
28
|
const PENDING_STALE_MS = 2 * 60 * 1000;
|
|
29
29
|
|
|
30
|
+
export function skippedFinalizeResult(meetingId: string) {
|
|
31
|
+
return {
|
|
32
|
+
meetingId,
|
|
33
|
+
summaryMd: "",
|
|
34
|
+
bullets: [],
|
|
35
|
+
actionItems: [],
|
|
36
|
+
provider: null,
|
|
37
|
+
skipped: "no-transcript" as const,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
function trimContextValue(value: string, maxChars: number): string {
|
|
31
42
|
const trimmed = value.replace(/\0/g, "").trim();
|
|
32
43
|
if (trimmed.length <= maxChars) return trimmed;
|
|
@@ -80,6 +91,11 @@ export default defineAction({
|
|
|
80
91
|
updatedAt: nowIso,
|
|
81
92
|
})
|
|
82
93
|
.where(eq(schema.meetings.id, args.meetingId));
|
|
94
|
+
// The desktop stop path can finish before its final transcript flush
|
|
95
|
+
// lands. That is a completed recording without notes, not an action
|
|
96
|
+
// failure worth returning as HTTP 500; the explicit regenerate flow
|
|
97
|
+
// still receives the actionable error below.
|
|
98
|
+
if (!args.force) return skippedFinalizeResult(args.meetingId);
|
|
83
99
|
throw new Error(
|
|
84
100
|
`Cannot finalize meeting ${args.meetingId} — no transcript text available yet.`,
|
|
85
101
|
);
|
|
@@ -23,6 +23,8 @@ import { z } from "zod";
|
|
|
23
23
|
|
|
24
24
|
import { getDb, schema } from "../server/db/index.js";
|
|
25
25
|
import {
|
|
26
|
+
recordCalendarFetchError,
|
|
27
|
+
recordCalendarFetchSuccess,
|
|
26
28
|
resolveCalendarAccessToken,
|
|
27
29
|
shouldMarkNeedsReauth,
|
|
28
30
|
} from "../server/lib/calendar-event-meetings.js";
|
|
@@ -98,24 +100,11 @@ export default defineAction({
|
|
|
98
100
|
// sync error via shouldMarkNeedsReauth without flipping status.
|
|
99
101
|
const accessToken = await resolveCalendarAccessToken(account);
|
|
100
102
|
if (!accessToken) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
.update(schema.calendarAccounts)
|
|
107
|
-
.set({
|
|
108
|
-
status: "needs-reauth",
|
|
109
|
-
lastSyncError: "Token refresh failed — reconnect required.",
|
|
110
|
-
updatedAt: new Date().toISOString(),
|
|
111
|
-
})
|
|
112
|
-
.where(eq(schema.calendarAccounts.id, account.id));
|
|
113
|
-
} catch (writeErr: any) {
|
|
114
|
-
console.warn(
|
|
115
|
-
`[sync-calendars] failed to flag account ${account.id} as needs-reauth:`,
|
|
116
|
-
writeErr?.message ?? writeErr,
|
|
117
|
-
);
|
|
118
|
-
}
|
|
103
|
+
await recordCalendarFetchError(
|
|
104
|
+
account,
|
|
105
|
+
new Error("Token refresh failed"),
|
|
106
|
+
{ needsReauth: true },
|
|
107
|
+
);
|
|
119
108
|
errors.push({
|
|
120
109
|
accountId: account.id,
|
|
121
110
|
error: "needs-reauth",
|
|
@@ -206,18 +195,10 @@ export default defineAction({
|
|
|
206
195
|
perAccountEvents += 1;
|
|
207
196
|
}
|
|
208
197
|
|
|
209
|
-
//
|
|
210
|
-
//
|
|
198
|
+
// Keep the success write isolated and guarded so an in-flight sweep
|
|
199
|
+
// cannot overwrite a concurrent needs-reauth decision.
|
|
211
200
|
try {
|
|
212
|
-
await
|
|
213
|
-
.update(schema.calendarAccounts)
|
|
214
|
-
.set({
|
|
215
|
-
lastSyncedAt: new Date().toISOString(),
|
|
216
|
-
lastSyncError: null,
|
|
217
|
-
status: "connected",
|
|
218
|
-
updatedAt: new Date().toISOString(),
|
|
219
|
-
})
|
|
220
|
-
.where(eq(schema.calendarAccounts.id, account.id));
|
|
201
|
+
await recordCalendarFetchSuccess(account);
|
|
221
202
|
} catch (writeErr: any) {
|
|
222
203
|
console.warn(
|
|
223
204
|
`[sync-calendars] failed to update account ${account.id} after success:`,
|
|
@@ -4,6 +4,7 @@ import { open as openExternal } from "@tauri-apps/plugin-shell";
|
|
|
4
4
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
5
5
|
|
|
6
6
|
import { callAppBundleIdsForJoinUrl } from "../lib/meeting-call-app";
|
|
7
|
+
import { stopMeetingBeforeTranscriptFlush } from "../lib/meeting-stop";
|
|
7
8
|
import {
|
|
8
9
|
appendFinalTranscript,
|
|
9
10
|
onFinalTranscript,
|
|
@@ -195,18 +196,33 @@ export function useMeetingTranscription({
|
|
|
195
196
|
}
|
|
196
197
|
});
|
|
197
198
|
await invoke("silence_detector_stop").catch(() => {});
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
199
|
+
await stopMeetingBeforeTranscriptFlush({
|
|
200
|
+
// Stamp actualEnd as soon as capture is torn down. Transcript
|
|
201
|
+
// history and network flushes may be slow or unavailable, but they
|
|
202
|
+
// must not leave the meeting looking live in the meantime.
|
|
203
|
+
stopRecording: async () => {
|
|
204
|
+
await callClipsAction("stop-meeting-recording", {
|
|
205
|
+
meetingId: session.meetingId,
|
|
206
|
+
}).catch((err) => {
|
|
207
|
+
console.warn("[clips-popover] stop meeting action failed:", err);
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
waitForHistory: async () => {
|
|
211
|
+
if (reason !== "app-quit") {
|
|
212
|
+
await session.historyInFlight?.catch(() => {});
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
// Final flush waits for any in-flight flush first
|
|
216
|
+
// (flushTranscript's single-flight coalescing) and sends the
|
|
217
|
+
// definitive snapshot.
|
|
218
|
+
flushTranscript: async () => {
|
|
219
|
+
await flushTranscript().catch((err) => {
|
|
220
|
+
console.warn(
|
|
221
|
+
"[clips-popover] meeting transcript save failed:",
|
|
222
|
+
err,
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
},
|
|
210
226
|
});
|
|
211
227
|
if (session.lines.length) {
|
|
212
228
|
const finalizePromise = callClipsAction("finalize-meeting", {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface StopMeetingBeforeTranscriptFlushOptions {
|
|
2
|
+
stopRecording: () => Promise<void>;
|
|
3
|
+
waitForHistory: () => Promise<void>;
|
|
4
|
+
flushTranscript: () => Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** Persist the terminal meeting state before waiting on transcript work. */
|
|
8
|
+
export async function stopMeetingBeforeTranscriptFlush({
|
|
9
|
+
stopRecording,
|
|
10
|
+
waitForHistory,
|
|
11
|
+
flushTranscript,
|
|
12
|
+
}: StopMeetingBeforeTranscriptFlushOptions): Promise<void> {
|
|
13
|
+
await stopRecording();
|
|
14
|
+
await waitForHistory();
|
|
15
|
+
await flushTranscript();
|
|
16
|
+
}
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
|
|
24
24
|
export const CALENDAR_MEETING_ID_PREFIX = "gcal";
|
|
25
25
|
|
|
26
|
+
const CALENDAR_REQUEST_SAFETY_MARGIN_MS = 60 * 1000;
|
|
27
|
+
|
|
26
28
|
export interface CalendarAccountForEvents {
|
|
27
29
|
id: string;
|
|
28
30
|
provider: string;
|
|
@@ -74,8 +76,7 @@ export function shouldMarkNeedsReauth(message: string): boolean {
|
|
|
74
76
|
lower.includes("google calendar event failed (401)") ||
|
|
75
77
|
lower.includes("invalid_grant") ||
|
|
76
78
|
lower.includes("invalid_token") ||
|
|
77
|
-
lower.includes("insufficient_scope")
|
|
78
|
-
lower.includes("token refresh failed")
|
|
79
|
+
lower.includes("insufficient_scope")
|
|
79
80
|
);
|
|
80
81
|
}
|
|
81
82
|
|
|
@@ -122,10 +123,16 @@ export async function resolveCalendarAccessToken(
|
|
|
122
123
|
});
|
|
123
124
|
} catch (err) {
|
|
124
125
|
// Only a permanent failure (dead refresh token / bad OAuth client) means
|
|
125
|
-
// "needs-reauth" — collapse those to `null` as before.
|
|
126
|
-
//
|
|
127
|
-
// record it as a soft sync error without flipping account status.
|
|
126
|
+
// "needs-reauth" — collapse those to `null` as before. During the refresh
|
|
127
|
+
// buffer, a transient failure can safely reuse the still-valid token.
|
|
128
128
|
if (isPermanentRefreshFailure(err)) return null;
|
|
129
|
+
if (
|
|
130
|
+
bundle?.accessToken &&
|
|
131
|
+
bundle.expiresAt &&
|
|
132
|
+
bundle.expiresAt > Date.now() + CALENDAR_REQUEST_SAFETY_MARGIN_MS
|
|
133
|
+
) {
|
|
134
|
+
return bundle.accessToken;
|
|
135
|
+
}
|
|
129
136
|
throw err;
|
|
130
137
|
}
|
|
131
138
|
if (!refreshed.access_token) return null;
|
|
@@ -158,13 +165,13 @@ export async function recordCalendarFetchSuccess(
|
|
|
158
165
|
.set({
|
|
159
166
|
lastSyncedAt: now,
|
|
160
167
|
lastSyncError: null,
|
|
161
|
-
status: "connected",
|
|
162
168
|
updatedAt: now,
|
|
163
169
|
})
|
|
164
170
|
.where(
|
|
165
171
|
and(
|
|
166
172
|
eq(schema.calendarAccounts.id, account.id),
|
|
167
173
|
eq(schema.calendarAccounts.ownerEmail, account.ownerEmail),
|
|
174
|
+
eq(schema.calendarAccounts.status, "connected"),
|
|
168
175
|
),
|
|
169
176
|
);
|
|
170
177
|
}
|
|
@@ -172,10 +179,11 @@ export async function recordCalendarFetchSuccess(
|
|
|
172
179
|
export async function recordCalendarFetchError(
|
|
173
180
|
account: CalendarAccountForEvents,
|
|
174
181
|
error: unknown,
|
|
182
|
+
options: { needsReauth?: boolean } = {},
|
|
175
183
|
): Promise<CalendarFetchError> {
|
|
176
184
|
const message =
|
|
177
185
|
error instanceof Error ? error.message : String(error || "Calendar failed");
|
|
178
|
-
const needsReauth = shouldMarkNeedsReauth(message);
|
|
186
|
+
const needsReauth = options.needsReauth ?? shouldMarkNeedsReauth(message);
|
|
179
187
|
if (!account.ownerEmail) {
|
|
180
188
|
return { accountId: account.id, error: message, needsReauth };
|
|
181
189
|
}
|
|
@@ -183,7 +191,7 @@ export async function recordCalendarFetchError(
|
|
|
183
191
|
await db
|
|
184
192
|
.update(schema.calendarAccounts)
|
|
185
193
|
.set({
|
|
186
|
-
|
|
194
|
+
...(needsReauth ? { status: "needs-reauth" as const } : {}),
|
|
187
195
|
lastSyncError: needsReauth
|
|
188
196
|
? "Google Calendar needs to be reconnected."
|
|
189
197
|
: message,
|
|
@@ -320,7 +328,9 @@ export async function fetchLiveCalendarEventFromId(virtualId: string) {
|
|
|
320
328
|
return null;
|
|
321
329
|
}
|
|
322
330
|
if (!accessToken) {
|
|
323
|
-
await recordCalendarFetchError(account, new Error("Token refresh failed")
|
|
331
|
+
await recordCalendarFetchError(account, new Error("Token refresh failed"), {
|
|
332
|
+
needsReauth: true,
|
|
333
|
+
});
|
|
324
334
|
return null;
|
|
325
335
|
}
|
|
326
336
|
|
|
@@ -16,6 +16,11 @@ explains them, but never hides a topology change in prose. Until an execution
|
|
|
16
16
|
binding is explicitly added, the blueprint must not be described as the runtime
|
|
17
17
|
router.
|
|
18
18
|
|
|
19
|
+
Runtime work — settings, inbox, rules, automations, and activity — is scoped by
|
|
20
|
+
`factoryId`. Reusable agents stay workspace-wide; graph nodes may reference any
|
|
21
|
+
workspace agent as blueprint only. Pass `factoryId` on triage, config, automation,
|
|
22
|
+
and audit actions for the factory the user is viewing.
|
|
23
|
+
|
|
19
24
|
## Workflow
|
|
20
25
|
|
|
21
26
|
1. Read `view-screen` to identify the selected Factory, tab, node, or edge.
|
|
@@ -21,22 +21,11 @@ decisions, feedback, agent runs, and provider audit records.
|
|
|
21
21
|
reconciliation is not success; preserve typed failure or
|
|
22
22
|
`reconciliation_required` state.
|
|
23
23
|
- Deduplicate by Factory item and rule/run identity, not provider comment ID.
|
|
24
|
-
- Slack clear bugs go through `start-builder-for-item
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
clarification limits, durable rechecks, and the required `@agent-native`
|
|
30
|
-
disposition after every actionable 👀. Clips, Design, and Content stay
|
|
31
|
-
owner-managed outside autonomous dispatch and PR governance.
|
|
32
|
-
- PR governance follows `review-prs`: verify BuilderIO membership and complete
|
|
33
|
-
diff/review/check evidence; skip drafts/current approvals and never approve
|
|
34
|
-
external authors. Keep ultra-scary security, auth, tenant-isolation,
|
|
35
|
-
secrets, data-loss, execution, payment, and deployment risks manual.
|
|
36
|
-
Verified members may pass ordinary uncertain checks or review feedback, but
|
|
37
|
-
record exact states and never call them clean. Apply the Alice/Content,
|
|
38
|
-
Nick/Slides, Enzo/Factory, Sid/Design, and docs-only exceptions only after
|
|
39
|
-
membership and the ultra-scary gate. Never auto-merge.
|
|
24
|
+
- Slack clear bugs go through `start-builder-for-item`; never post Slack
|
|
25
|
+
messages or `@handles`. GitHub/Sentry use the Builder run API. Read
|
|
26
|
+
`review-latest-feedback` for thread evidence and disposition rules.
|
|
27
|
+
- PR governance follows `review-prs`: verify membership and evidence; skip
|
|
28
|
+
drafts and external authors; keep ultra-scary risks manual; never auto-merge.
|
|
40
29
|
- Graph edits create immutable blueprint versions. AI proposes with `source=ai`;
|
|
41
30
|
a person reviews and publishes through the same action surface.
|
|
42
31
|
- Provider credentials belong to Dispatch/shared workspace integrations, never
|
|
@@ -46,16 +35,16 @@ decisions, feedback, agent runs, and provider audit records.
|
|
|
46
35
|
|
|
47
36
|
## Application state
|
|
48
37
|
|
|
49
|
-
- `navigation.view` is `factory
|
|
50
|
-
|
|
51
|
-
`
|
|
52
|
-
`factoryEdgeId` hold Factory context. Read `view-screen` first.
|
|
38
|
+
- `navigation.view` is `factory` or `agents`. Runtime data is scoped by
|
|
39
|
+
`factoryId`; reusable agents stay workspace-wide. Read `view-screen` and
|
|
40
|
+
`factory-graphs` for tab and selection keys.
|
|
53
41
|
|
|
54
42
|
## Action contract
|
|
55
43
|
|
|
56
44
|
| Action | Purpose |
|
|
57
45
|
| --- | --- |
|
|
58
|
-
| `list-triage-items` / `get-triage-item` | Inspect
|
|
46
|
+
| `list-triage-items` / `get-triage-item` | Inspect queue evidence; pass `factoryId`. |
|
|
47
|
+
| `get-triage-config` / `save-triage-config` | Read or save observation settings for one factory. |
|
|
59
48
|
| `poll-slack-channel` | Observe Slack history; never writes to Slack. |
|
|
60
49
|
| `get-slack-feedback-context` | Read the bounded full Slack thread before classification. |
|
|
61
50
|
| `poll-github-sources` / `poll-sentry-errors` | Observe bounded source queues. |
|
|
@@ -67,11 +56,12 @@ decisions, feedback, agent runs, and provider audit records.
|
|
|
67
56
|
| `start-builder-for-item` | Govern clear-bug dispatch through Slack or Builder API, or record a skip reason. |
|
|
68
57
|
| `govern-agent-native-pull-request` | Apply PR evidence and ownership gates. |
|
|
69
58
|
| `list-factory-automations` / `save-factory-automation` / `run-factory-automation` | Inspect or edit org-owned automations. |
|
|
70
|
-
| `list-factory-audit` | Inspect
|
|
59
|
+
| `list-factory-audit` | Inspect runs, evidence, and provider actions for one factory. |
|
|
71
60
|
| `get-factory-automation-health` | Inspect scheduler heartbeat and last error. |
|
|
72
61
|
| `suggest-factory-rules` | Mine feedback into proposals. |
|
|
73
62
|
| `reconcile-triage-run` | Persist callback/provider reconciliation. |
|
|
74
63
|
| `list-factories` / `get-factory-graph` | Inspect definitions, versions, and metrics. |
|
|
64
|
+
| `create-factory` | Create a factory from `/new-factory` with optional sources. |
|
|
75
65
|
| `save-factory-graph` | Create/version a graph with inspected `expectedGraphVersion`; never starts provider work. |
|
|
76
66
|
| graph history actions | Factory graph version history. |
|
|
77
67
|
| `list-factory-comments` / `add-factory-comment` | Read or attach comments to a canvas, node, or edge. |
|
|
@@ -44,19 +44,22 @@ shared `app_secrets` rows must use the same
|
|
|
44
44
|
encryption fallback). Never copy raw tokens between apps or add a second
|
|
45
45
|
env-only read in a provider client.
|
|
46
46
|
|
|
47
|
-
Factory's
|
|
48
|
-
|
|
49
|
-
queue adapters.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
Factory's observer keeps **per-factory** source metadata — Slack channel,
|
|
48
|
+
repository, Sentry project, and related polling settings — for its normalized
|
|
49
|
+
queue adapters. Each factory has its own inbox, rules, automations, and activity.
|
|
50
|
+
Reusable agents and workspace credentials stay shared. Each factory's observation
|
|
51
|
+
settings live on its Settings tab in the factory detail view. That metadata is
|
|
52
|
+
not a credential or a per-factory
|
|
53
|
+
integration setup. GitHub issue polling defaults to hourly; Slack and
|
|
54
|
+
pull-request checks use shorter bounded cadences because those sources can need
|
|
55
|
+
faster feedback. Every poll preserves errors for reconciliation.
|
|
53
56
|
|
|
54
57
|
Factory agents can discover connected provider APIs with
|
|
55
58
|
`provider-api-catalog`, inspect their docs with `provider-api-docs`, and call
|
|
56
59
|
them through `provider-api-request`. Scheduled Factory runs also receive the
|
|
57
60
|
workspace's connected MCP tools, subject to the same workspace and request
|
|
58
|
-
scope gates. The three normalized pollers are compatibility adapters
|
|
59
|
-
|
|
61
|
+
scope gates. The three normalized pollers are compatibility adapters scoped by
|
|
62
|
+
`factoryId`, not the agent's capability limit.
|
|
60
63
|
|
|
61
64
|
The generic Slack bot is wired to Factory. Mention `@agent-native` in a feedback
|
|
62
65
|
thread to inspect the linked item, explain its decision, tune a rule, or say
|