@agent-native/core-corpus 0.203.1-nightly-20261007183952 → 0.203.1

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.
@@ -11,6 +11,10 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
11
11
  - Save exact session replay screens as PNG screenshots.
12
12
  - Session replays can be saved as screenshots without the replay controls.
13
13
 
14
+ ### Improved
15
+
16
+ - Agent dashboard edits are now checked against the live chart before they save, so the agent no longer reports a chart change that did not happen.
17
+
14
18
  ### Fixed
15
19
 
16
20
  - Collaborators see a consistent dashboard after simultaneous edits
@@ -7,6 +7,8 @@ time from the command menu (Cmd+K → "What's new").
7
7
 
8
8
  ### Fixed
9
9
 
10
+ - ChatGPT widget previews show saved document bodies without a Content session.
11
+ - Documents opened in ChatGPT show their saved content right away.
10
12
  - ChatGPT widgets show saved document bodies in read-only mode without a Content session.
11
13
  - Fixed Content pages that opened to an error in ChatGPT widgets
12
14
 
@@ -3,6 +3,13 @@
3
3
  All notable user-facing changes to Design are documented here. Open it any time
4
4
  from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-10-07
7
+
8
+ ### Fixed
9
+
10
+ - Design keeps frame-relative positions and selections stable when moving or pasting layers.
11
+ - Position values stay aligned with selected layers in layouts with body margins.
12
+
6
13
  ## 2026-10-06
7
14
 
8
15
  ### Added
@@ -46,10 +46,25 @@ and pass normalized action items to an app's idempotent `import` action.
46
46
 
47
47
  | Tool | Action | Purpose |
48
48
  | ------------- | ---------- | ---------------------------------------------------------- |
49
- | `manage-jobs` | `create` | Create a recurring job (name, cron schedule, instructions) |
49
+ | `manage-jobs` | `create` | Create a recurring job (name, cron schedule, instructions); fails if the name exists |
50
50
  | `manage-jobs` | `list` | List all jobs and their status |
51
51
  | `manage-jobs` | `update` | Update schedule, instructions, or toggle enabled |
52
52
 
53
+ ## Job files an app owns
54
+
55
+ `create` never replaces an existing job file, so a name that collides with an
56
+ app's file (Factory's `factories/<id>/<name>`) is refused instead of reset.
57
+ An app that stores its own settings in a job's frontmatter marks the file with
58
+ `triggerType`, which makes `list`, `update`, and `delete` skip it and sends the
59
+ caller to `manage-automations`. Edit those files only through the owning app's
60
+ actions, and patch fields in place; rebuilding a file from the fields the writer
61
+ knows drops the rest while the job keeps running.
62
+
63
+ Any write to a `jobs/` file that removes a frontmatter field other than run
64
+ bookkeeping is recorded as a `job-fields-dropped` audit event (org admins read
65
+ it with `list-audit-events` and `action: "job-fields-dropped"`). Its input names
66
+ the writer, the dropped fields, and the agent thread and run that made it.
67
+
53
68
  ## UI Surface
54
69
 
55
70
  `/agent#jobs` is the stable compatibility URL for the Agent page's
@@ -24,11 +24,12 @@ import {
24
24
  readPromptVersion,
25
25
  replaceAutomationContentWithUserPrompt,
26
26
  scheduleCron,
27
+ stampAutomationTriggerType,
27
28
  } from "../server/lib/factory-automation-config.js";
28
29
  import {
29
30
  deleteFactoryAutomationVersionRow,
30
31
  insertFactoryAutomationVersionIfChanged,
31
- resolvePromptVersionForSnapshot,
32
+ resolvePromptVersionAllocation,
32
33
  snapshotFromAutomationResource,
33
34
  } from "../server/lib/factory-automation-history.js";
34
35
  import { findFactoryAutomationDefinition } from "../server/lib/factory-automation-resources.js";
@@ -249,14 +250,17 @@ export default defineAction({
249
250
  input.displayName !== undefined
250
251
  ? input.displayName.trim() || null
251
252
  : previousSnapshot.displayName;
252
- const resolvedPromptVersion = resolvePromptVersionForSnapshot(
253
- {
253
+ const versionAllocation = await resolvePromptVersionAllocation({
254
+ automationId: definition.resource.id,
255
+ orgId,
256
+ next: {
254
257
  userPrompt: normalizedPrompt,
255
258
  displayName: nextDisplayName,
256
259
  config,
257
260
  },
258
- previousSnapshot,
259
- );
261
+ previous: previousSnapshot,
262
+ });
263
+ const resolvedPromptVersion = versionAllocation.promptVersion;
260
264
  let content = applyAutomationConfigFrontmatter(resource.content, config);
261
265
  content = replaceAutomationContentWithUserPrompt(
262
266
  content,
@@ -289,6 +293,13 @@ export default defineAction({
289
293
  input.factoryId,
290
294
  );
291
295
  content = setAutomationFrontmatterField(content, "appId", "factory");
296
+ const stamp = stampAutomationTriggerType(content, { orgId });
297
+ content = stamp.content;
298
+ if (stamp.skipped) {
299
+ console.warn(
300
+ `[save-factory-automation] ${input.name} stays untagged because ${stamp.skipped}.`,
301
+ );
302
+ }
292
303
  if (input.model !== undefined) {
293
304
  content = setAutomationFrontmatterField(
294
305
  content,
@@ -328,6 +339,7 @@ export default defineAction({
328
339
  nextContent: content,
329
340
  summary: "Automation save",
330
341
  source: "save",
342
+ version: versionAllocation.predecessorVersion ?? undefined,
331
343
  });
332
344
  let updated: Awaited<ReturnType<typeof resourcePutIfCurrent>> = null;
333
345
  let writeError: unknown;
@@ -490,6 +490,61 @@ export function restoreFactoryAutomationIdentityFields(
490
490
  return next;
491
491
  }
492
492
 
493
+ export type AutomationTriggerStamp = {
494
+ content: string;
495
+ /** Why the file stays untagged; absent when it is tagged. */
496
+ skipped?: string;
497
+ };
498
+
499
+ /**
500
+ * Tags a job file with its trigger type so core treats it as an automation.
501
+ * The tag moves the scheduler from its legacy identity fallback to strict
502
+ * checks (`createdBy`, `runAs: creator`, and an `orgId` matching the owner),
503
+ * so a file is tagged only when it can pass them. Defaults the legacy path
504
+ * already assumes are written down; a creator is never invented.
505
+ */
506
+ export function stampAutomationTriggerType(
507
+ content: string,
508
+ options: { orgId: string; triggerType?: string; identityFrom?: string },
509
+ ): AutomationTriggerStamp {
510
+ if (readFrontmatterValue(content, "triggerType") != null) return { content };
511
+ const read = (key: string) =>
512
+ readFrontmatterValue(content, key) ??
513
+ (options.identityFrom
514
+ ? readFrontmatterValue(options.identityFrom, key)
515
+ : undefined);
516
+ const createdBy = read("createdBy");
517
+ if (!createdBy) return { content, skipped: "it has no createdBy" };
518
+ const runAs = read("runAs") ?? "creator";
519
+ if (runAs !== "creator") {
520
+ return { content, skipped: `it runs as "${runAs}"` };
521
+ }
522
+ const orgId = read("orgId") ?? options.orgId;
523
+ if (orgId !== options.orgId) {
524
+ return {
525
+ content,
526
+ skipped: `its orgId is "${orgId}", not "${options.orgId}"`,
527
+ };
528
+ }
529
+ let next = content;
530
+ for (const [key, value] of [
531
+ ["createdBy", createdBy],
532
+ ["runAs", runAs],
533
+ ["orgId", orgId],
534
+ ] as const) {
535
+ if (readFrontmatterValue(next, key) == null) {
536
+ next = setAutomationFrontmatterField(next, key, value);
537
+ }
538
+ }
539
+ return {
540
+ content: setAutomationFrontmatterField(
541
+ next,
542
+ "triggerType",
543
+ options.triggerType ?? "schedule",
544
+ ),
545
+ };
546
+ }
547
+
493
548
  export function applyAutomationConfigFrontmatter(
494
549
  content: string,
495
550
  config: FactoryAutomationConfig,
@@ -1,5 +1,7 @@
1
+ import { fail } from "@agent-native/core/action";
2
+ import { isUniqueViolation } from "@agent-native/core/db";
1
3
  import { type Resource } from "@agent-native/core/resources";
2
- import { and, desc, eq, lt } from "drizzle-orm";
4
+ import { and, desc, eq, lt, max } from "drizzle-orm";
3
5
 
4
6
  import { getDb } from "../db/index.js";
5
7
  import { factoryAutomationVersions } from "../db/schema.js";
@@ -13,6 +15,7 @@ import {
13
15
  readFactoryAutomationConfig,
14
16
  readFrontmatterValue,
15
17
  readPromptVersion,
18
+ stampAutomationTriggerType,
16
19
  } from "./factory-automation-config.js";
17
20
  import { findFactoryAutomationByResourceId } from "./factory-automation-resources.js";
18
21
  import {
@@ -74,17 +77,73 @@ export function snapshotContentIdentity(
74
77
  });
75
78
  }
76
79
 
77
- export function resolvePromptVersionForSnapshot(
80
+ async function maxStoredVersion(input: {
81
+ automationId: string;
82
+ orgId: string;
83
+ }): Promise<number | null> {
84
+ const [row] = await getDb()
85
+ .select({ latest: max(factoryAutomationVersions.version) })
86
+ .from(factoryAutomationVersions)
87
+ .where(
88
+ and(
89
+ eq(factoryAutomationVersions.automationId, input.automationId),
90
+ eq(factoryAutomationVersions.orgId, input.orgId),
91
+ ),
92
+ );
93
+ return row?.latest ?? null;
94
+ }
95
+
96
+ /**
97
+ * The version number to store the previous content under. A file's own
98
+ * `promptVersion` is the right number until it falls behind its history: a
99
+ * file that lost its frontmatter reads as version 0, and storing that under a
100
+ * number the history already holds fails the whole save.
101
+ */
102
+ export async function allocatePredecessorVersion(input: {
103
+ automationId: string;
104
+ orgId: string;
105
+ fileVersion: number;
106
+ }): Promise<number> {
107
+ const stored = await maxStoredVersion(input);
108
+ const nextFree = stored === null ? 0 : stored + 1;
109
+ if (input.fileVersion >= nextFree) return input.fileVersion;
110
+ console.warn(
111
+ `[factory-automation-history] ${input.automationId} reports promptVersion ${input.fileVersion} but versions up to ${stored} are already stored; recording its previous content as version ${nextFree}.`,
112
+ );
113
+ return nextFree;
114
+ }
115
+
116
+ export type PromptVersionAllocation = {
117
+ /** The `promptVersion` the saved file carries. */
118
+ promptVersion: number;
119
+ /** Where the previous content is stored; null when nothing changed. */
120
+ predecessorVersion: number | null;
121
+ };
122
+
123
+ export async function resolvePromptVersionAllocation(input: {
124
+ automationId: string;
125
+ orgId: string;
78
126
  next: Pick<
79
127
  FactoryAutomationSnapshot,
80
128
  "userPrompt" | "displayName" | "config"
81
- >,
82
- previous: FactoryAutomationSnapshot,
83
- ): number {
84
- if (snapshotContentIdentity(previous) === snapshotContentIdentity(next)) {
85
- return previous.promptVersion;
129
+ >;
130
+ previous: FactoryAutomationSnapshot;
131
+ }): Promise<PromptVersionAllocation> {
132
+ if (
133
+ snapshotContentIdentity(input.previous) ===
134
+ snapshotContentIdentity(input.next)
135
+ ) {
136
+ return {
137
+ promptVersion: input.previous.promptVersion,
138
+ predecessorVersion: null,
139
+ };
86
140
  }
87
- return previous.promptVersion + 1;
141
+ const predecessorVersion = await allocatePredecessorVersion({
142
+ automationId: input.automationId,
143
+ orgId: input.orgId,
144
+ fileVersion: input.previous.promptVersion,
145
+ });
146
+ return { promptVersion: predecessorVersion + 1, predecessorVersion };
88
147
  }
89
148
 
90
149
  export type FactoryAutomationVersionRow = {
@@ -115,17 +174,26 @@ export async function insertFactoryAutomationVersionRow(input: {
115
174
  content: string;
116
175
  summary: string;
117
176
  source: FactoryAutomationVersionSource;
177
+ /** Pre-allocated by `resolvePromptVersionAllocation`; allocated here when omitted. */
178
+ version?: number;
118
179
  }): Promise<FactoryAutomationVersionRow> {
119
180
  const snapshot = snapshotFromAutomationResource(
120
181
  input.content,
121
182
  input.automationName,
122
183
  input.factoryId,
123
184
  );
185
+ const version =
186
+ input.version ??
187
+ (await allocatePredecessorVersion({
188
+ automationId: input.automationId,
189
+ orgId: input.orgId,
190
+ fileVersion: snapshot.promptVersion,
191
+ }));
124
192
  const row: FactoryAutomationVersionRow = {
125
193
  id: createVersionId(),
126
194
  automationId: input.automationId,
127
195
  factoryId: input.factoryId,
128
- version: snapshot.promptVersion,
196
+ version,
129
197
  rawContent: input.content,
130
198
  displayName: snapshot.displayName,
131
199
  source: input.source,
@@ -135,7 +203,21 @@ export async function insertFactoryAutomationVersionRow(input: {
135
203
  ownerEmail: input.userEmail,
136
204
  orgId: input.orgId,
137
205
  };
138
- await getDb().insert(factoryAutomationVersions).values(row);
206
+ try {
207
+ await getDb().insert(factoryAutomationVersions).values(row);
208
+ } catch (error) {
209
+ // Drizzle wraps the driver error, so the constraint code is on `cause`.
210
+ if (
211
+ isUniqueViolation(error) ||
212
+ isUniqueViolation((error as { cause?: unknown }).cause)
213
+ ) {
214
+ fail(
215
+ "Another change to this automation was saved at the same time. Refresh and try again.",
216
+ { statusCode: 409, errorCode: "automation_version_conflict" },
217
+ );
218
+ }
219
+ throw error;
220
+ }
139
221
  return row;
140
222
  }
141
223
 
@@ -149,6 +231,7 @@ export async function insertFactoryAutomationVersionIfChanged(input: {
149
231
  nextContent: string;
150
232
  summary: string;
151
233
  source: FactoryAutomationVersionSource;
234
+ version?: number;
152
235
  }): Promise<FactoryAutomationVersionRow | null> {
153
236
  const previousSnapshot = snapshotFromAutomationResource(
154
237
  input.previousContent,
@@ -175,6 +258,7 @@ export async function insertFactoryAutomationVersionIfChanged(input: {
175
258
  content: input.previousContent,
176
259
  summary: input.summary,
177
260
  source: input.source,
261
+ version: input.version,
178
262
  });
179
263
  }
180
264
 
@@ -305,10 +389,13 @@ export async function restoreFactoryAutomationVersion(input: {
305
389
  input.automationName,
306
390
  input.factoryId,
307
391
  );
308
- const resolvedVersion = resolvePromptVersionForSnapshot(
309
- restoredSnapshot,
310
- previousSnapshot,
311
- );
392
+ const allocation = await resolvePromptVersionAllocation({
393
+ automationId: input.automationId,
394
+ orgId: input.orgId,
395
+ next: restoredSnapshot,
396
+ previous: previousSnapshot,
397
+ });
398
+ const resolvedVersion = allocation.promptVersion;
312
399
  const configSavedAt = new Date().toISOString();
313
400
 
314
401
  let content = preserveOperationalFrontmatterFields(
@@ -335,6 +422,20 @@ export async function restoreFactoryAutomationVersion(input: {
335
422
  "factoryId",
336
423
  input.factoryId,
337
424
  );
425
+ // Versions saved before the stamp existed lack it; restoring one must not
426
+ // take it off the live file, nor tag a file that cannot pass the strict
427
+ // identity check.
428
+ const stamp = stampAutomationTriggerType(content, {
429
+ orgId: input.orgId,
430
+ triggerType: readFrontmatterValue(current.content, "triggerType"),
431
+ identityFrom: current.content,
432
+ });
433
+ content = stamp.content;
434
+ if (stamp.skipped) {
435
+ console.warn(
436
+ `[factory-automation-history] ${input.automationName} stays untagged after the restore because ${stamp.skipped}.`,
437
+ );
438
+ }
338
439
 
339
440
  const insertedVersion = await insertFactoryAutomationVersionIfChanged({
340
441
  automationId: input.automationId,
@@ -346,6 +447,7 @@ export async function restoreFactoryAutomationVersion(input: {
346
447
  nextContent: content,
347
448
  summary: input.summary,
348
449
  source: "restore",
450
+ version: allocation.predecessorVersion ?? undefined,
349
451
  });
350
452
 
351
453
  let updated: Awaited<ReturnType<typeof resourcePutIfCurrent>> = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core-corpus",
3
- "version": "0.203.1-nightly-20261007183952",
3
+ "version": "0.203.1",
4
4
  "description": "Opt-in source corpus for Agent-Native Core source-search.",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {