@agent-native/core 0.164.11 → 0.164.15
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/.agents/skills/event-management/SKILL.md +84 -0
- package/corpus/templates/calendar/AGENTS.md +5 -0
- package/corpus/templates/calendar/actions/delete-event.ts +1 -1
- package/corpus/templates/calendar/actions/delete-events.ts +508 -0
- package/corpus/templates/calendar/actions/event-action-helpers.ts +1 -1
- package/corpus/templates/calendar/actions/list-events.ts +53 -0
- package/corpus/templates/calendar/changelog/2026-08-18-the-agent-can-now-remove-many-meetings-at-once-such-as-every.md +6 -0
- package/corpus/templates/calendar/server/lib/event-weekday.ts +112 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +25 -2
- package/corpus/templates/content/actions/_database-utils.ts +2 -0
- package/corpus/templates/content/actions/_local-folder-source.ts +186 -2
- package/corpus/templates/content/actions/connect-local-folder-source.ts +23 -8
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +3 -3
- package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +2 -2
- package/corpus/templates/content/actions/sync-local-folder-source.ts +130 -12
- package/corpus/templates/content/app/components/LocalFolderLiveSync.tsx +77 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +354 -8
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +23 -1
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +78 -1
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +36 -0
- package/corpus/templates/content/app/components/editor/local-source-write-state.ts +52 -0
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +244 -6
- package/corpus/templates/content/app/components/sidebar/local-source-hierarchy.ts +189 -0
- package/corpus/templates/content/app/hooks/use-create-page.ts +26 -4
- package/corpus/templates/content/app/hooks/use-documents.ts +83 -0
- package/corpus/templates/content/app/i18n-data.ts +79 -0
- package/corpus/templates/content/app/lib/desktop-content-files.ts +57 -1
- package/corpus/templates/content/app/lib/local-content-source-files.ts +210 -34
- package/corpus/templates/content/app/lib/local-folder-live-sync.ts +191 -0
- package/corpus/templates/content/app/root.tsx +2 -1
- package/corpus/templates/content/app/routes/_app.local-files.tsx +103 -38
- package/corpus/templates/content/docs/product/capabilities/content.source.file-folder.md +2 -2
- package/corpus/templates/content/docs/product/features/content.feature.bring-your-local-work.md +3 -3
- package/corpus/templates/content/docs/product/roadmap.md +2 -2
- package/corpus/templates/content/docs/solutions/2026-08-18-content-navigation-coherence-shape.md +482 -0
- package/corpus/templates/content/shared/api.ts +25 -0
- package/corpus/templates/factory/AGENTS.md +2 -1
- package/corpus/templates/factory/actions/get-factory-graph-version.ts +69 -0
- package/corpus/templates/factory/actions/list-factory-graph-versions.ts +82 -0
- package/corpus/templates/factory/actions/restore-factory-graph-version.ts +163 -0
- package/corpus/templates/factory/actions/save-factory-graph.ts +93 -60
- package/corpus/templates/factory/app/components/factory/FactoryHistoryView.tsx +709 -0
- package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +62 -2
- package/corpus/templates/factory/app/components/ui/alert-dialog.tsx +1 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +43 -0
- package/corpus/templates/factory/app/routes/factory.tsx +115 -15
- package/corpus/templates/factory/server/plugins/agent-chat.ts +3 -1
- package/dist/app-config/auth.d.ts +4 -0
- package/dist/app-config/auth.js +7 -0
- package/dist/app-config/schema.d.ts +3 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/cli/code-agent-executor.js +6 -1
- package/dist/cli/code-agent-mcp-config.d.ts +2 -2
- package/dist/cli/code-agent-mcp-config.js +34 -9
- package/dist/client/org/OrgSwitcher.js +8 -4
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/mcp-client/config.d.ts +11 -0
- package/dist/mcp-client/config.js +33 -26
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +15 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/docs/content/actions-access-control.mdx +136 -2
- package/docs/content/actions-agent-tools.mdx +101 -0
- package/docs/content/actions-defining.mdx +2 -2
- package/docs/content/audit-log.mdx +1 -1
- package/docs/content/authentication.mdx +1 -1
- package/docs/content/cloudflare.mdx +43 -0
- package/docs/content/creating-templates.mdx +1 -1
- package/docs/content/deploy-an-app.mdx +109 -0
- package/docs/content/deployment-environment-variables.mdx +101 -0
- package/docs/content/deployment.mdx +125 -792
- package/docs/content/docs-components.mdx +2 -2
- package/docs/content/environment-variables.mdx +41 -41
- package/docs/content/file-uploads.mdx +74 -40
- package/docs/content/harness-agents.mdx +2 -2
- package/docs/content/internationalization.mdx +61 -65
- package/docs/content/key-concepts.mdx +3 -3
- package/docs/content/multi-app-workspace.mdx +2 -2
- package/docs/content/netlify.mdx +47 -0
- package/docs/content/node-docker.mdx +138 -0
- package/docs/content/organizations-teams-permissions.mdx +5 -5
- package/docs/content/other-platforms.mdx +27 -0
- package/docs/content/ssr-caching.mdx +45 -0
- package/docs/content/template-assets-developers.mdx +2 -2
- package/docs/content/template-chat-developers.mdx +2 -2
- package/docs/content/template-content-developers.mdx +2 -2
- package/docs/content/{local-file-mode.mdx → template-content-local-files.mdx} +2 -1
- package/docs/content/template-content-sync.mdx +2 -2
- package/docs/content/toolkit-agent-ux.mdx +1 -1
- package/docs/content/toolkit-resources.mdx +2 -2
- package/docs/content/updating-ui-in-production.mdx +35 -0
- package/docs/content/vercel.mdx +38 -0
- package/docs/content/what-is-agent-native.mdx +1 -1
- package/docs/content/workspace-deployment.mdx +350 -0
- package/package.json +1 -1
- package/docs/content/agents.mdx +0 -9
- package/docs/content/database.mdx +0 -317
- package/docs/content/human-approval.mdx +0 -156
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
2
2
|
import { useT } from "@agent-native/core/client/i18n";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
IconAlertCircle,
|
|
5
|
+
IconArrowRight,
|
|
6
|
+
IconLoader2,
|
|
7
|
+
IconPlus,
|
|
8
|
+
IconTrash,
|
|
9
|
+
} from "@tabler/icons-react";
|
|
4
10
|
import { useMemo, useState } from "react";
|
|
5
11
|
import { useSearchParams } from "react-router";
|
|
6
12
|
|
|
@@ -22,8 +28,13 @@ interface FactoryInspectorProps {
|
|
|
22
28
|
factoryId?: string;
|
|
23
29
|
dirty: boolean;
|
|
24
30
|
saving: boolean;
|
|
31
|
+
saveError: string | null;
|
|
32
|
+
saveConflictNeedsResolution: boolean;
|
|
33
|
+
refreshing: boolean;
|
|
25
34
|
onGraphChange: (graph: FactoryCanvasGraph) => void;
|
|
26
35
|
onSave: () => void;
|
|
36
|
+
onRefresh: () => void;
|
|
37
|
+
onDiscardLocalChanges: () => void;
|
|
27
38
|
onAddNode: () => void;
|
|
28
39
|
onDeleteNode: (nodeId: string) => void;
|
|
29
40
|
onConnect: (sourceId: string, targetId: string) => void;
|
|
@@ -54,8 +65,13 @@ export function FactoryInspector({
|
|
|
54
65
|
factoryId,
|
|
55
66
|
dirty,
|
|
56
67
|
saving,
|
|
68
|
+
saveError,
|
|
69
|
+
saveConflictNeedsResolution,
|
|
70
|
+
refreshing,
|
|
57
71
|
onGraphChange,
|
|
58
72
|
onSave,
|
|
73
|
+
onRefresh,
|
|
74
|
+
onDiscardLocalChanges,
|
|
59
75
|
onAddNode,
|
|
60
76
|
onDeleteNode,
|
|
61
77
|
onConnect,
|
|
@@ -383,12 +399,56 @@ export function FactoryInspector({
|
|
|
383
399
|
</section>
|
|
384
400
|
</div>
|
|
385
401
|
|
|
402
|
+
{saveError ? (
|
|
403
|
+
<div
|
|
404
|
+
className="flex items-start gap-2 border-t border-border/60 bg-destructive/5 p-4 text-sm text-destructive"
|
|
405
|
+
role="alert"
|
|
406
|
+
>
|
|
407
|
+
<IconAlertCircle className="mt-0.5 size-4 shrink-0" />
|
|
408
|
+
<div className="min-w-0 flex-1 space-y-2">
|
|
409
|
+
<p>{saveError}</p>
|
|
410
|
+
{saveConflictNeedsResolution ? (
|
|
411
|
+
<p className="text-xs leading-5 text-destructive/80">
|
|
412
|
+
{t("factoryInspector.saveConflictHint")}
|
|
413
|
+
</p>
|
|
414
|
+
) : null}
|
|
415
|
+
<div className="flex flex-wrap gap-2">
|
|
416
|
+
{!saveConflictNeedsResolution ? (
|
|
417
|
+
<Button
|
|
418
|
+
type="button"
|
|
419
|
+
variant="link"
|
|
420
|
+
size="sm"
|
|
421
|
+
className="h-auto p-0 text-destructive"
|
|
422
|
+
disabled={refreshing}
|
|
423
|
+
onClick={onRefresh}
|
|
424
|
+
>
|
|
425
|
+
{refreshing ? (
|
|
426
|
+
<IconLoader2 className="size-3.5 animate-spin" />
|
|
427
|
+
) : null}
|
|
428
|
+
{t("triage.refresh")}
|
|
429
|
+
</Button>
|
|
430
|
+
) : (
|
|
431
|
+
<Button
|
|
432
|
+
type="button"
|
|
433
|
+
variant="link"
|
|
434
|
+
size="sm"
|
|
435
|
+
className="h-auto p-0 text-destructive"
|
|
436
|
+
onClick={onDiscardLocalChanges}
|
|
437
|
+
>
|
|
438
|
+
{t("factoryInspector.discardLocalChanges")}
|
|
439
|
+
</Button>
|
|
440
|
+
)}
|
|
441
|
+
</div>
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
) : null}
|
|
445
|
+
|
|
386
446
|
{(dirty || saving) && (
|
|
387
447
|
<div className="bg-muted/15 p-4">
|
|
388
448
|
<Button
|
|
389
449
|
type="button"
|
|
390
450
|
className="w-full"
|
|
391
|
-
disabled={saving}
|
|
451
|
+
disabled={saving || refreshing}
|
|
392
452
|
onClick={onSave}
|
|
393
453
|
>
|
|
394
454
|
{saving
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@agent-native/toolkit/ui/alert-dialog";
|
|
@@ -204,6 +204,46 @@ const messages = {
|
|
|
204
204
|
auditUxImpact: "UX impact",
|
|
205
205
|
auditOwnerArea: "Owner area",
|
|
206
206
|
auditGuardsLabel: "Guards",
|
|
207
|
+
historyTab: "History",
|
|
208
|
+
historyTitle: "Saved versions",
|
|
209
|
+
historyLoading: "Loading saved versions",
|
|
210
|
+
historyLoadError: "Could not load Factory history.",
|
|
211
|
+
historyEmpty: "No saved versions yet",
|
|
212
|
+
historyEmptyHint:
|
|
213
|
+
"Save the visual graph once and Factory will keep each version here for review and rollback.",
|
|
214
|
+
historyVersion: "Version {{version}}",
|
|
215
|
+
historyCurrent: "Current",
|
|
216
|
+
historySource: "Source",
|
|
217
|
+
historySourceManual: "Visual editor",
|
|
218
|
+
historySourceAi: "Agent proposal",
|
|
219
|
+
historySourceSeed: "Factory seed",
|
|
220
|
+
historySourceRestore: "Rollback",
|
|
221
|
+
historySourceSaved: "Saved graph",
|
|
222
|
+
historyNodes: "Nodes",
|
|
223
|
+
historyConnections: "Connections",
|
|
224
|
+
historyChangeSummary: "Change summary",
|
|
225
|
+
historyNoSummary: "No change summary was recorded.",
|
|
226
|
+
historyCurrentHint: "This is the version currently used by the Factory.",
|
|
227
|
+
historyRestore: "Restore this version",
|
|
228
|
+
historyRestoreTitle: "Restore version {{version}}?",
|
|
229
|
+
historyRestoreDescription:
|
|
230
|
+
"Factory will create a new current version from this snapshot. Existing versions stay available in history.",
|
|
231
|
+
historyRestoreUnsaved:
|
|
232
|
+
"You also have unsaved visual changes. Restoring will discard them.",
|
|
233
|
+
historyCancel: "Cancel",
|
|
234
|
+
historyRestoreConfirm: "Restore version",
|
|
235
|
+
historyRestored: "Version restored. A new current version was created.",
|
|
236
|
+
historyRestoreFailed: "Could not restore this Factory version.",
|
|
237
|
+
historyRestoreConflict:
|
|
238
|
+
"Factory changed while restoring. Review the refreshed history before trying again.",
|
|
239
|
+
historyRefreshFailed:
|
|
240
|
+
"The version was restored, but the Factory view could not refresh.",
|
|
241
|
+
historyLoadOlder: "Load older versions",
|
|
242
|
+
historySnapshotLoadError: "Could not load this version preview.",
|
|
243
|
+
historyRestoreUnverified:
|
|
244
|
+
"Restore completed, but the result could not be verified. Refresh history before trying again.",
|
|
245
|
+
saveConflictFallback:
|
|
246
|
+
"Factory changed while saving. Refresh Factory and try again.",
|
|
207
247
|
automationsTitle: "Automations",
|
|
208
248
|
automationsDescription: "Schedule and run Factory prompts.",
|
|
209
249
|
automationsLoading: "Loading automations...",
|
|
@@ -312,6 +352,9 @@ const messages = {
|
|
|
312
352
|
addComment: "Add comment",
|
|
313
353
|
savingGraph: "Saving graph...",
|
|
314
354
|
saveGraph: "Save factory changes",
|
|
355
|
+
saveConflictHint:
|
|
356
|
+
"Your unsaved edits are preserved. Discard them to load the newer Factory version.",
|
|
357
|
+
discardLocalChanges: "Discard local edits and load current version",
|
|
315
358
|
savedGraph: "Factory is saved",
|
|
316
359
|
saveNote: "Saves a new version. Existing runs keep their original context.",
|
|
317
360
|
factoryName: "Factory name",
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
type FactoryCanvasGraph,
|
|
32
32
|
type FactoryCanvasNode,
|
|
33
33
|
} from "@/components/factory/FactoryCanvas";
|
|
34
|
+
import { FactoryHistoryView } from "@/components/factory/FactoryHistoryView";
|
|
34
35
|
import { FactoryInspector } from "@/components/factory/FactoryInspector";
|
|
35
36
|
import { TriageStatusPill } from "@/components/triage/triage-status-pill";
|
|
36
37
|
import { Button } from "@/components/ui/button";
|
|
@@ -131,6 +132,7 @@ type WorkspaceTab =
|
|
|
131
132
|
| "automations"
|
|
132
133
|
| "agents"
|
|
133
134
|
| "audit"
|
|
135
|
+
| "history"
|
|
134
136
|
| "settings";
|
|
135
137
|
|
|
136
138
|
type FactoryAutomationRun = {
|
|
@@ -184,9 +186,14 @@ export default function FactoryRoute() {
|
|
|
184
186
|
const [creating, setCreating] = useState(false);
|
|
185
187
|
const [draftGraph, setDraftGraph] = useState<FactoryCanvasGraph | null>(null);
|
|
186
188
|
const [dirty, setDirty] = useState(false);
|
|
189
|
+
const [saveError, setSaveError] = useState<string | null>(null);
|
|
190
|
+
const [saveConflictRemoteGraph, setSaveConflictRemoteGraph] =
|
|
191
|
+
useState<FactoryCanvasGraph | null>(null);
|
|
192
|
+
const [refreshingFactory, setRefreshingFactory] = useState(false);
|
|
187
193
|
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
|
|
188
194
|
const [selectedEdgeId, setSelectedEdgeId] = useState<string | null>(null);
|
|
189
195
|
const [auditRefreshToken, setAuditRefreshToken] = useState(0);
|
|
196
|
+
const draftRevisionRef = useRef(0);
|
|
190
197
|
|
|
191
198
|
function setActiveTab(tab: WorkspaceTab) {
|
|
192
199
|
setSearchParams(
|
|
@@ -194,6 +201,10 @@ export default function FactoryRoute() {
|
|
|
194
201
|
const next = new URLSearchParams(current);
|
|
195
202
|
if (tab === "overview") next.delete("tab");
|
|
196
203
|
else next.set("tab", tab);
|
|
204
|
+
if (tab === "history") {
|
|
205
|
+
next.delete("node");
|
|
206
|
+
next.delete("edge");
|
|
207
|
+
}
|
|
197
208
|
return next;
|
|
198
209
|
},
|
|
199
210
|
{ replace: true },
|
|
@@ -255,17 +266,17 @@ export default function FactoryRoute() {
|
|
|
255
266
|
const graphData =
|
|
256
267
|
rawGraphData?.factory.id === factoryId ? rawGraphData : undefined;
|
|
257
268
|
const graph = draftGraph ?? graphData?.graph ?? null;
|
|
258
|
-
const graphVersion = graphData?.factory.graphVersion ??
|
|
269
|
+
const graphVersion = graph?.version ?? graphData?.factory.graphVersion ?? 1;
|
|
259
270
|
const saveGraphMutation = useActionMutation("save-factory-graph");
|
|
260
271
|
const factoryList = (factoryListQuery.data ?? []) as FactorySummary[];
|
|
261
272
|
|
|
262
273
|
useEffect(() => {
|
|
263
|
-
if (!graphData || creating) return;
|
|
274
|
+
if (!graphData || creating || dirty) return;
|
|
264
275
|
setDraftGraph(graphData.graph);
|
|
265
276
|
setDirty(false);
|
|
266
277
|
setSelectedNodeId(null);
|
|
267
278
|
setSelectedEdgeId(null);
|
|
268
|
-
}, [creating, graphData]);
|
|
279
|
+
}, [creating, dirty, graphData]);
|
|
269
280
|
|
|
270
281
|
useEffect(() => {
|
|
271
282
|
setSelectedNodeId(searchParams.get("node"));
|
|
@@ -313,6 +324,9 @@ export default function FactoryRoute() {
|
|
|
313
324
|
}
|
|
314
325
|
|
|
315
326
|
function updateGraph(next: FactoryCanvasGraph) {
|
|
327
|
+
draftRevisionRef.current += 1;
|
|
328
|
+
setSaveError(null);
|
|
329
|
+
setSaveConflictRemoteGraph(null);
|
|
316
330
|
setDraftGraph(next);
|
|
317
331
|
setDirty(
|
|
318
332
|
!graphData?.graph ||
|
|
@@ -396,20 +410,87 @@ export default function FactoryRoute() {
|
|
|
396
410
|
|
|
397
411
|
async function saveGraph() {
|
|
398
412
|
if (!graph || !selectedFactoryId) return;
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
? "
|
|
407
|
-
: "
|
|
408
|
-
|
|
409
|
-
|
|
413
|
+
const submittedDraftRevision = draftRevisionRef.current;
|
|
414
|
+
setSaveError(null);
|
|
415
|
+
try {
|
|
416
|
+
await saveGraphMutation.mutateAsync({
|
|
417
|
+
factoryId: selectedFactoryId,
|
|
418
|
+
name: graph.name,
|
|
419
|
+
description: graph.description,
|
|
420
|
+
prompt: creating ? "" : (graphData?.factory.prompt ?? ""),
|
|
421
|
+
source: "manual",
|
|
422
|
+
changeSummary: creating
|
|
423
|
+
? "Created from the Factory visual editor."
|
|
424
|
+
: "Updated in the Factory visual editor.",
|
|
425
|
+
expectedGraphVersion: creating ? 0 : graph.version,
|
|
426
|
+
graph,
|
|
427
|
+
});
|
|
428
|
+
setCreating(false);
|
|
429
|
+
setDirty(draftRevisionRef.current !== submittedDraftRevision);
|
|
430
|
+
setSaveConflictRemoteGraph(null);
|
|
431
|
+
await Promise.all([graphQuery.refetch(), factoryListQuery.refetch()]);
|
|
432
|
+
} catch (error) {
|
|
433
|
+
setSaveError(
|
|
434
|
+
error instanceof Error
|
|
435
|
+
? error.message
|
|
436
|
+
: t("factoryRoute.saveConflictFallback"),
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function refreshFactoryAfterSaveConflict() {
|
|
442
|
+
setRefreshingFactory(true);
|
|
443
|
+
try {
|
|
444
|
+
const results = await Promise.all([
|
|
445
|
+
graphQuery.refetch(),
|
|
446
|
+
factoryListQuery.refetch(),
|
|
447
|
+
]);
|
|
448
|
+
if (results.some((result) => result.isError)) {
|
|
449
|
+
throw new Error("Factory refresh failed.");
|
|
450
|
+
}
|
|
451
|
+
const remoteGraph = (results[0].data as FactoryGraphResponse | undefined)
|
|
452
|
+
?.graph;
|
|
453
|
+
if (dirty) {
|
|
454
|
+
if (!remoteGraph) throw new Error("Factory graph refresh failed.");
|
|
455
|
+
setSaveConflictRemoteGraph(remoteGraph);
|
|
456
|
+
setSaveError(t("factoryRoute.saveConflictFallback"));
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
setSaveError(null);
|
|
460
|
+
} catch {
|
|
461
|
+
setSaveError(t("factoryRoute.saveConflictFallback"));
|
|
462
|
+
} finally {
|
|
463
|
+
setRefreshingFactory(false);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function discardLocalFactoryChanges() {
|
|
468
|
+
if (!saveConflictRemoteGraph) return;
|
|
469
|
+
setDraftGraph(saveConflictRemoteGraph);
|
|
470
|
+
setSaveConflictRemoteGraph(null);
|
|
471
|
+
setSaveError(null);
|
|
472
|
+
setDirty(false);
|
|
473
|
+
setCreating(false);
|
|
474
|
+
setSelectedNodeId(null);
|
|
475
|
+
setSelectedEdgeId(null);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async function handleFactoryRestored(result?: { graph: FactoryCanvasGraph }) {
|
|
410
479
|
setCreating(false);
|
|
480
|
+
if (result?.graph) setDraftGraph(result.graph);
|
|
481
|
+
else setDraftGraph(null);
|
|
411
482
|
setDirty(false);
|
|
412
|
-
|
|
483
|
+
setSaveError(null);
|
|
484
|
+
setSaveConflictRemoteGraph(null);
|
|
485
|
+
setSelectedNodeId(null);
|
|
486
|
+
setSelectedEdgeId(null);
|
|
487
|
+
const results = await Promise.all([
|
|
488
|
+
graphQuery.refetch(),
|
|
489
|
+
factoryListQuery.refetch(),
|
|
490
|
+
]);
|
|
491
|
+
if (results.some((result) => result.isError)) {
|
|
492
|
+
throw new Error("Factory view refresh failed.");
|
|
493
|
+
}
|
|
413
494
|
}
|
|
414
495
|
|
|
415
496
|
if (!selectedFactoryId && activeTab === "agents") {
|
|
@@ -659,6 +740,12 @@ export default function FactoryRoute() {
|
|
|
659
740
|
>
|
|
660
741
|
Activity
|
|
661
742
|
</TabButton>
|
|
743
|
+
<TabButton
|
|
744
|
+
active={activeTab === "history"}
|
|
745
|
+
onClick={() => setActiveTab("history")}
|
|
746
|
+
>
|
|
747
|
+
{t("factoryRoute.historyTab")}
|
|
748
|
+
</TabButton>
|
|
662
749
|
<TabButton
|
|
663
750
|
active={activeTab === "settings"}
|
|
664
751
|
onClick={() => setActiveTab("settings")}
|
|
@@ -727,8 +814,13 @@ export default function FactoryRoute() {
|
|
|
727
814
|
factoryId={factoryId}
|
|
728
815
|
dirty={dirty}
|
|
729
816
|
saving={saveGraphMutation.isPending}
|
|
817
|
+
saveError={saveError}
|
|
818
|
+
saveConflictNeedsResolution={Boolean(saveConflictRemoteGraph)}
|
|
819
|
+
refreshing={refreshingFactory}
|
|
730
820
|
onGraphChange={updateGraph}
|
|
731
821
|
onSave={() => void saveGraph()}
|
|
822
|
+
onRefresh={refreshFactoryAfterSaveConflict}
|
|
823
|
+
onDiscardLocalChanges={discardLocalFactoryChanges}
|
|
732
824
|
onAddNode={addNode}
|
|
733
825
|
onDeleteNode={deleteNode}
|
|
734
826
|
onConnect={connectNodes}
|
|
@@ -747,6 +839,13 @@ export default function FactoryRoute() {
|
|
|
747
839
|
factoryId={factoryId}
|
|
748
840
|
refreshToken={auditRefreshToken}
|
|
749
841
|
/>
|
|
842
|
+
) : activeTab === "history" ? (
|
|
843
|
+
<FactoryHistoryView
|
|
844
|
+
key={factoryId}
|
|
845
|
+
factoryId={factoryId}
|
|
846
|
+
hasUnsavedChanges={dirty}
|
|
847
|
+
onRestored={handleFactoryRestored}
|
|
848
|
+
/>
|
|
750
849
|
) : (
|
|
751
850
|
<SettingsView t={t} />
|
|
752
851
|
)}
|
|
@@ -783,6 +882,7 @@ function parseWorkspaceTab(value: string | null): WorkspaceTab {
|
|
|
783
882
|
value === "automations" ||
|
|
784
883
|
value === "agents" ||
|
|
785
884
|
value === "audit" ||
|
|
885
|
+
value === "history" ||
|
|
786
886
|
value === "settings"
|
|
787
887
|
? value
|
|
788
888
|
: "overview";
|
|
@@ -57,7 +57,9 @@ current factory graph, and executes only the explicit automation prompts that
|
|
|
57
57
|
are stored in the organization.
|
|
58
58
|
Use the Factory actions as the source of truth. When a user asks to
|
|
59
59
|
create or change a factory, first inspect the current graph, then propose a complete
|
|
60
|
-
versioned graph through save-factory-graph with source=ai
|
|
60
|
+
versioned graph through save-factory-graph with source=ai, the inspected graphVersion
|
|
61
|
+
as expectedGraphVersion, and a concise changeSummary. Never save a graph from a
|
|
62
|
+
stale read: refresh and re-propose when the action reports a version conflict.
|
|
61
63
|
Never hide a graph change in prose: the visual map and the saved graph must agree.
|
|
62
64
|
The graph is currently a reviewable blueprint, not the runtime router: automation
|
|
63
65
|
markdown resources are the runtime prompts, while enabled triage rules are evaluated
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const authConfig = z.object({
|
|
3
|
+
disableDesktopSsoFallbackInDevelopment: z.boolean().default(false).meta({
|
|
4
|
+
env: "AGENT_NATIVE_DISABLE_DESKTOP_SSO_FALLBACK",
|
|
5
|
+
doc: "Disable the loopback Desktop SSO fallback in development so isolated acceptance runs can use their configured local identity. Ignored in production.",
|
|
6
|
+
}),
|
|
7
|
+
});
|
|
@@ -32,6 +32,9 @@ export declare const appConfigSchema: z.ZodObject<{
|
|
|
32
32
|
packageName: z.ZodOptional<z.ZodString>;
|
|
33
33
|
template: z.ZodOptional<z.ZodString>;
|
|
34
34
|
}, z.core.$strip>>;
|
|
35
|
+
auth: z.ZodPrefault<z.ZodObject<{
|
|
36
|
+
disableDesktopSsoFallbackInDevelopment: z.ZodDefault<z.ZodBoolean>;
|
|
37
|
+
}, z.core.$strip>>;
|
|
35
38
|
integrations: z.ZodPrefault<z.ZodObject<{
|
|
36
39
|
allowUnverifiedWebhooks: z.ZodDefault<z.ZodBoolean>;
|
|
37
40
|
}, z.core.$strip>>;
|
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { a2aConfig } from "./a2a.js";
|
|
3
3
|
import { agentConfig } from "./agent.js";
|
|
4
4
|
import { appConfig } from "./app.js";
|
|
5
|
+
import { authConfig } from "./auth.js";
|
|
5
6
|
import { integrationsConfig } from "./integrations.js";
|
|
6
7
|
import { privateBlobConfig } from "./private-blob.js";
|
|
7
8
|
import { workspaceConfig } from "./workspace.js";
|
|
@@ -25,6 +26,7 @@ export const appConfigSchema = z.object({
|
|
|
25
26
|
a2a: a2aConfig.prefault({}),
|
|
26
27
|
agent: agentConfig.prefault({}),
|
|
27
28
|
app: appConfig.prefault({}),
|
|
29
|
+
auth: authConfig.prefault({}),
|
|
28
30
|
integrations: integrationsConfig.prefault({}),
|
|
29
31
|
privateBlob: privateBlobConfig.prefault({}),
|
|
30
32
|
workspace: workspaceConfig.prefault({}),
|
|
@@ -1226,8 +1226,13 @@ async function executeCodexCliRun(options) {
|
|
|
1226
1226
|
const streamToolOutputToStdout = options.streamToolOutputToStdout ??
|
|
1227
1227
|
process.env.AGENT_NATIVE_CODE_AGENT_STRUCTURED_STDOUT !== "1";
|
|
1228
1228
|
const additionalSkillsRoot = process.env.AGENT_NATIVE_CODE_AGENT_SKILLS_ROOT?.trim();
|
|
1229
|
+
// Desktop has already resolved and capability-scoped its MCP catalog before
|
|
1230
|
+
// spawning the packaged runner. Reading persisted settings again here would
|
|
1231
|
+
// both widen that boundary and require the app's native database binding in
|
|
1232
|
+
// the standalone runner process.
|
|
1233
|
+
const mcpConfig = process.env.MCP_SERVERS === undefined ? await buildMergedConfig() : null;
|
|
1229
1234
|
const args = [
|
|
1230
|
-
...codexMcpConfigArgs(),
|
|
1235
|
+
...codexMcpConfigArgs(mcpConfig),
|
|
1231
1236
|
"--ask-for-approval",
|
|
1232
1237
|
"never",
|
|
1233
1238
|
"--sandbox",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type McpConfig } from "../mcp-client/config.js";
|
|
2
2
|
/**
|
|
3
3
|
* Keep a desktop coding run on the app registry's MCP servers. The normal
|
|
4
4
|
* server-side manager merges persisted MCP settings for web requests, but a
|
|
@@ -18,4 +18,4 @@ export declare function mergeCodeAgentMcpConfig(config: McpConfig | null, enviro
|
|
|
18
18
|
* coding session receives the workspace apps, not the user's global MCP
|
|
19
19
|
* catalog.
|
|
20
20
|
*/
|
|
21
|
-
export declare function codexMcpConfigArgs(environment?: NodeJS.ProcessEnv): string[];
|
|
21
|
+
export declare function codexMcpConfigArgs(config?: McpConfig | null, environment?: NodeJS.ProcessEnv): string[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveDesktopChildComputerMcpServer, } from "../mcp-client/config.js";
|
|
1
2
|
const DESKTOP_MCP_ALLOWLIST_ENV = "AGENT_NATIVE_CODE_AGENT_MCP_SERVER_ALLOWLIST";
|
|
2
3
|
function parseServerAllowlist(raw) {
|
|
3
4
|
if (raw === undefined)
|
|
@@ -17,6 +18,9 @@ export function restrictCodeAgentMcpConfig(config, environment = process.env) {
|
|
|
17
18
|
const allowlist = parseServerAllowlist(environment[DESKTOP_MCP_ALLOWLIST_ENV]);
|
|
18
19
|
if (!allowlist || !config)
|
|
19
20
|
return config;
|
|
21
|
+
const desktopServer = findDesktopChildComputerMcpServer(config, environment);
|
|
22
|
+
if (desktopServer)
|
|
23
|
+
allowlist.add(desktopServer.id);
|
|
20
24
|
const servers = Object.fromEntries(Object.entries(config.servers).filter(([id]) => allowlist.has(id)));
|
|
21
25
|
return { ...config, servers };
|
|
22
26
|
}
|
|
@@ -59,6 +63,18 @@ function parseMcpServers(environment) {
|
|
|
59
63
|
return [id, value];
|
|
60
64
|
}));
|
|
61
65
|
}
|
|
66
|
+
function desktopChildComputerServerConfig(environment) {
|
|
67
|
+
return resolveDesktopChildComputerMcpServer({}, environment)?.config ?? null;
|
|
68
|
+
}
|
|
69
|
+
function findDesktopChildComputerMcpServer(config, environment) {
|
|
70
|
+
const expected = desktopChildComputerServerConfig(environment);
|
|
71
|
+
if (!expected || expected.type !== "http")
|
|
72
|
+
return null;
|
|
73
|
+
const match = Object.entries(config.servers).find(([, server]) => server.type === "http" &&
|
|
74
|
+
server.url === expected.url &&
|
|
75
|
+
server.headers?.Authorization === expected.headers?.Authorization);
|
|
76
|
+
return match ? { id: match[0], config: match[1] } : null;
|
|
77
|
+
}
|
|
62
78
|
/**
|
|
63
79
|
* Add host-provided app/plugin servers to the normal merged config. The
|
|
64
80
|
* filesystem and environment layers have different precedence rules, so the
|
|
@@ -66,14 +82,23 @@ function parseMcpServers(environment) {
|
|
|
66
82
|
*/
|
|
67
83
|
export function mergeCodeAgentMcpConfig(config, environment = process.env) {
|
|
68
84
|
const environmentServers = parseMcpServers(environment);
|
|
69
|
-
|
|
85
|
+
const servers = {
|
|
86
|
+
...(config?.servers ?? {}),
|
|
87
|
+
...environmentServers,
|
|
88
|
+
};
|
|
89
|
+
const existingDesktopServer = config
|
|
90
|
+
? findDesktopChildComputerMcpServer(config, environment)
|
|
91
|
+
: null;
|
|
92
|
+
const desktopServer = existingDesktopServer
|
|
93
|
+
? null
|
|
94
|
+
: resolveDesktopChildComputerMcpServer(servers, environment);
|
|
95
|
+
if (desktopServer)
|
|
96
|
+
servers[desktopServer.id] = desktopServer.config;
|
|
97
|
+
if (Object.keys(servers).length === 0)
|
|
70
98
|
return config;
|
|
71
99
|
return {
|
|
72
100
|
...(config ?? { servers: {} }),
|
|
73
|
-
servers
|
|
74
|
-
...(config?.servers ?? {}),
|
|
75
|
-
...environmentServers,
|
|
76
|
-
},
|
|
101
|
+
servers,
|
|
77
102
|
source: config?.source
|
|
78
103
|
? `${config.source}+desktop-environment`
|
|
79
104
|
: "env:MCP_SERVERS",
|
|
@@ -85,8 +110,8 @@ export function mergeCodeAgentMcpConfig(config, environment = process.env) {
|
|
|
85
110
|
* coding session receives the workspace apps, not the user's global MCP
|
|
86
111
|
* catalog.
|
|
87
112
|
*/
|
|
88
|
-
export function codexMcpConfigArgs(environment = process.env) {
|
|
89
|
-
const servers =
|
|
113
|
+
export function codexMcpConfigArgs(config = null, environment = process.env) {
|
|
114
|
+
const servers = restrictCodeAgentMcpConfig(mergeCodeAgentMcpConfig(config, environment), environment)?.servers ?? {};
|
|
90
115
|
if (Object.keys(servers).length === 0)
|
|
91
116
|
return [];
|
|
92
117
|
const args = [];
|
|
@@ -94,9 +119,9 @@ export function codexMcpConfigArgs(environment = process.env) {
|
|
|
94
119
|
if (server.type !== "http" || !server.url)
|
|
95
120
|
continue;
|
|
96
121
|
const key = codexConfigKey(serverId);
|
|
97
|
-
args.push("-c", `mcp_servers.${
|
|
122
|
+
args.push("-c", `mcp_servers.${key}.url=${tomlString(server.url)}`);
|
|
98
123
|
if (server.headers && Object.keys(server.headers).length > 0) {
|
|
99
|
-
args.push("-c", `mcp_servers.${
|
|
124
|
+
args.push("-c", `mcp_servers.${key}.http_headers=${tomlInlineTable(server.headers)}`);
|
|
100
125
|
}
|
|
101
126
|
}
|
|
102
127
|
return args;
|
|
@@ -7,6 +7,7 @@ import { setBrowserDemoModeEnabled } from "../../demo/browser-state.js";
|
|
|
7
7
|
import { shouldOfferWorkspace } from "../../org/workspace-url.js";
|
|
8
8
|
import { agentNativePath } from "../api-path.js";
|
|
9
9
|
import { useT } from "../i18n.js";
|
|
10
|
+
import { buildSignInReturnHref } from "../require-session.js";
|
|
10
11
|
import { useDemoModeStatus } from "../use-demo-mode-status.js";
|
|
11
12
|
import { notifySessionInvalidated, useSession } from "../use-session.js";
|
|
12
13
|
import { useOrg, useSwitchOrg, useCreateOrg, useInviteMember, useAcceptInvitation, useJoinByDomain, } from "./hooks.js";
|
|
@@ -142,16 +143,19 @@ export function OrgSwitcher({ className, hideWhenSingle, reserveSpace, compact,
|
|
|
142
143
|
return;
|
|
143
144
|
setSigningOut(true);
|
|
144
145
|
try {
|
|
145
|
-
await fetch(agentNativePath("/_agent-native/auth/logout"), {
|
|
146
|
+
const response = await fetch(agentNativePath("/_agent-native/auth/logout"), {
|
|
146
147
|
method: "POST",
|
|
147
148
|
credentials: "include",
|
|
148
149
|
});
|
|
150
|
+
if (!response.ok) {
|
|
151
|
+
console.warn("Logout request returned an error before sign-in", response.status);
|
|
152
|
+
}
|
|
149
153
|
}
|
|
150
|
-
catch {
|
|
151
|
-
|
|
154
|
+
catch (error) {
|
|
155
|
+
console.warn("Unable to complete logout request before sign-in", error);
|
|
152
156
|
}
|
|
153
157
|
notifySessionInvalidated();
|
|
154
|
-
window.location.
|
|
158
|
+
window.location.replace(buildSignInReturnHref());
|
|
155
159
|
};
|
|
156
160
|
if (!org) {
|
|
157
161
|
return reserveSpace && isLoading ? (_jsx(OrgSwitcherLoadingPlaceholder, { className: className })) : null;
|
|
@@ -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
|
}>>;
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
26
26
|
* Body: { update: string (base64), requestSource?: string }
|
|
27
27
|
*/
|
|
28
28
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
29
|
-
error: string;
|
|
30
29
|
ok?: undefined;
|
|
30
|
+
error: string;
|
|
31
31
|
} | {
|
|
32
32
|
error?: undefined;
|
|
33
33
|
ok: boolean;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
ok?: undefined;
|
|
17
16
|
error: string;
|
|
17
|
+
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
@@ -60,12 +60,23 @@ export interface McpConfig {
|
|
|
60
60
|
/** Where the config was loaded from (workspace root path, app path, or "env") */
|
|
61
61
|
source?: string;
|
|
62
62
|
}
|
|
63
|
+
export interface DesktopChildComputerMcpServer {
|
|
64
|
+
id: string;
|
|
65
|
+
config: McpHttpServerConfig;
|
|
66
|
+
}
|
|
63
67
|
/**
|
|
64
68
|
* Load MCP configuration.
|
|
65
69
|
*
|
|
66
70
|
* @param startDir - Directory to start the upward search from (defaults to cwd)
|
|
67
71
|
*/
|
|
68
72
|
export declare function loadMcpConfig(startDir?: string): McpConfig | null;
|
|
73
|
+
/**
|
|
74
|
+
* The desktop process supplies this configuration only to a child it spawned
|
|
75
|
+
* for a specific Agent run. Raw MCP config cannot opt into this trust path:
|
|
76
|
+
* the explicit child gate, loopback URL, and strong per-run bearer are all
|
|
77
|
+
* required. Existing user servers always win on key collisions.
|
|
78
|
+
*/
|
|
79
|
+
export declare function resolveDesktopChildComputerMcpServer(existingServers: Record<string, McpServerConfig>, environment?: NodeJS.ProcessEnv): DesktopChildComputerMcpServer | null;
|
|
69
80
|
/**
|
|
70
81
|
* Auto-detect the claude-in-chrome MCP server if it's installed but no
|
|
71
82
|
* config file exists. Gated by `AGENT_NATIVE_DISABLE_MCP_AUTODETECT`.
|