@agent-native/core 0.101.6 → 0.101.7

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.
Files changed (101) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/artifact-response.ts +255 -12
  5. package/corpus/core/src/agent/thread-data-builder.ts +84 -11
  6. package/corpus/core/src/integrations/a2a-continuation-processor.ts +99 -7
  7. package/corpus/core/src/integrations/adapters/discord.ts +3 -1
  8. package/corpus/core/src/integrations/adapters/email.ts +4 -1
  9. package/corpus/core/src/integrations/adapters/google-docs.ts +4 -1
  10. package/corpus/core/src/integrations/adapters/microsoft-teams.ts +3 -1
  11. package/corpus/core/src/integrations/adapters/slack.ts +38 -9
  12. package/corpus/core/src/integrations/adapters/telegram.ts +32 -10
  13. package/corpus/core/src/integrations/adapters/whatsapp.ts +5 -1
  14. package/corpus/core/src/integrations/index.ts +1 -0
  15. package/corpus/core/src/integrations/types.ts +9 -2
  16. package/corpus/core/src/integrations/webhook-handler.ts +69 -45
  17. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +1 -0
  18. package/corpus/templates/content/.agents/skills/content/SKILL.md +31 -0
  19. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +35 -21
  20. package/corpus/templates/content/AGENTS.md +29 -7
  21. package/corpus/templates/content/actions/_database-source-utils.ts +30 -16
  22. package/corpus/templates/content/actions/_database-utils.ts +15 -1
  23. package/corpus/templates/content/actions/_local-file-documents.ts +53 -1
  24. package/corpus/templates/content/actions/_property-utils.ts +7 -1
  25. package/corpus/templates/content/actions/configure-document-property.ts +11 -0
  26. package/corpus/templates/content/actions/create-content-database.ts +11 -0
  27. package/corpus/templates/content/actions/create-document.ts +9 -0
  28. package/corpus/templates/content/actions/create-inline-content-database.ts +3 -0
  29. package/corpus/templates/content/actions/export-content-source.ts +1 -0
  30. package/corpus/templates/content/actions/get-content-database.ts +13 -1
  31. package/corpus/templates/content/actions/get-document.ts +12 -2
  32. package/corpus/templates/content/actions/import-content-source.ts +6 -0
  33. package/corpus/templates/content/actions/list-documents.ts +3 -0
  34. package/corpus/templates/content/actions/pull-document.ts +2 -0
  35. package/corpus/templates/content/actions/search-documents.ts +5 -1
  36. package/corpus/templates/content/actions/update-document.ts +16 -1
  37. package/corpus/templates/content/actions/view-screen.ts +15 -2
  38. package/corpus/templates/content/app/components/editor/DescriptionField.tsx +121 -0
  39. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +14 -0
  40. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +223 -34
  41. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +56 -8
  42. package/corpus/templates/content/app/i18n/zh-TW.ts +5 -0
  43. package/corpus/templates/content/app/i18n-data.ts +52 -0
  44. package/corpus/templates/content/changelog/2026-07-14-pages-and-databases-can-describe-themselves-with-guidance-fo.md +6 -0
  45. package/corpus/templates/content/changelog/2026-07-14-slack-follow-ups-now-retain-created-content-identity-and-per.md +6 -0
  46. package/corpus/templates/content/server/db/schema.ts +4 -0
  47. package/corpus/templates/content/server/lib/document-context.ts +76 -0
  48. package/corpus/templates/content/server/lib/public-documents.ts +3 -0
  49. package/corpus/templates/content/server/plugins/db.ts +6 -0
  50. package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +5 -0
  51. package/corpus/templates/content/shared/api.ts +17 -0
  52. package/corpus/templates/content/shared/content-source.ts +7 -0
  53. package/corpus/templates/content/shared/properties.ts +2 -0
  54. package/dist/a2a/artifact-response.d.ts +15 -0
  55. package/dist/a2a/artifact-response.d.ts.map +1 -1
  56. package/dist/a2a/artifact-response.js +204 -5
  57. package/dist/a2a/artifact-response.js.map +1 -1
  58. package/dist/agent/thread-data-builder.d.ts +6 -0
  59. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  60. package/dist/agent/thread-data-builder.js +71 -8
  61. package/dist/agent/thread-data-builder.js.map +1 -1
  62. package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
  63. package/dist/integrations/a2a-continuation-processor.js +72 -6
  64. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  65. package/dist/integrations/adapters/discord.d.ts.map +1 -1
  66. package/dist/integrations/adapters/discord.js +1 -0
  67. package/dist/integrations/adapters/discord.js.map +1 -1
  68. package/dist/integrations/adapters/email.d.ts.map +1 -1
  69. package/dist/integrations/adapters/email.js +2 -0
  70. package/dist/integrations/adapters/email.js.map +1 -1
  71. package/dist/integrations/adapters/google-docs.d.ts.map +1 -1
  72. package/dist/integrations/adapters/google-docs.js +2 -0
  73. package/dist/integrations/adapters/google-docs.js.map +1 -1
  74. package/dist/integrations/adapters/microsoft-teams.d.ts.map +1 -1
  75. package/dist/integrations/adapters/microsoft-teams.js +1 -0
  76. package/dist/integrations/adapters/microsoft-teams.js.map +1 -1
  77. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  78. package/dist/integrations/adapters/slack.js +21 -6
  79. package/dist/integrations/adapters/slack.js.map +1 -1
  80. package/dist/integrations/adapters/telegram.d.ts.map +1 -1
  81. package/dist/integrations/adapters/telegram.js +11 -2
  82. package/dist/integrations/adapters/telegram.js.map +1 -1
  83. package/dist/integrations/adapters/whatsapp.d.ts.map +1 -1
  84. package/dist/integrations/adapters/whatsapp.js +3 -0
  85. package/dist/integrations/adapters/whatsapp.js.map +1 -1
  86. package/dist/integrations/index.d.ts +1 -1
  87. package/dist/integrations/index.d.ts.map +1 -1
  88. package/dist/integrations/index.js.map +1 -1
  89. package/dist/integrations/types.d.ts +8 -2
  90. package/dist/integrations/types.d.ts.map +1 -1
  91. package/dist/integrations/types.js.map +1 -1
  92. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  93. package/dist/integrations/webhook-handler.js +40 -43
  94. package/dist/integrations/webhook-handler.js.map +1 -1
  95. package/dist/observability/routes.d.ts +1 -1
  96. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  97. package/dist/resources/handlers.d.ts +1 -1
  98. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
  99. package/dist/server/agent-chat/action-filters-a2a.js +1 -0
  100. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
  101. package/package.json +1 -1
@@ -102,19 +102,19 @@ cd templates/content && pnpm action <name> [args]
102
102
  | `push-builder-doc` | `--files <json> [--path <file.builder.mdx>] [--dryRun true\|false]` | Guarded Builder autosave PATCH for the safe Builder test model; never publishes |
103
103
  | `navigate` | `--path <path>` or `--documentId <id>` or `--databaseId <id>` | Open a route, document page, or database page in the UI |
104
104
  | `search-documents` | `--query <text> [--format json]` | Search by title/content and return snippets |
105
- | `get-document` | `--id <id> [--format json]` | Get a single document with content |
105
+ | `get-document` | `--id <id> [--format json]` | Get a document with content, stable description, properties, and computed ancestry context |
106
106
  | `pull-document` | `--id <id> [--format markdown\|text]` | Collab-aware "ingest the final" read |
107
- | `create-document` | `--title <text> [--content] [--parentId] [--icon]` | Create a new document |
107
+ | `create-document` | `--title <text> [--content] [--description] [--parentId] [--icon]` | Create a document with optional stable guidance about why it exists and what belongs there |
108
108
  | `edit-document` | `--id <id> --find <text> --replace <text>` | Surgical text edit (preferred for modifications) |
109
109
  | `edit-document` | `--id <id> --edits <json>` | Batch surgical text edits |
110
- | `update-document` | `--id <id> [--title] [--content] [--icon]` | Full rewrite of document fields |
110
+ | `update-document` | `--id <id> [--title] [--content] [--description] [--icon]` | Update document fields; descriptions are stable guidance, not summaries of current content |
111
111
  | `share-local-file-document` | `--id <local-file-document-id>` | Create or refresh a DB-backed shareable copy of a local file document |
112
112
  | `remove-local-file-source` | `[--sourceRootPath <path>]` | Unlink local-file sources from Content without deleting local Markdown/MDX files |
113
113
  | `list-local-component-files` | | List registered local MDX component source files |
114
114
  | `write-local-component-file` | `--workspaceId <id> --path <relative-component-path> --content <source>` | Create or update a file in a registered local `components/` folder |
115
- | `create-content-database` | `[--documentId <id>] [--parentId <id>] [--title <text>]` | Create a database page or convert an existing page into a database |
116
- | `create-inline-content-database` | `--hostDocumentId <id> [--title <text>]` | Create a database owned by an inline database block in the host document |
117
- | `get-content-database` | `--databaseId <id>` or `--documentId <id>` | Get a database table with property schema and item pages |
115
+ | `create-content-database` | `[--documentId <id>] [--parentId <id>] [--title <text>] [--description <text>]` | Create a described database page or convert an existing page into a database |
116
+ | `create-inline-content-database` | `--hostDocumentId <id> [--title <text>] [--description <text>]` | Create a described database owned by an inline database block in the host document |
117
+ | `get-content-database` | `--databaseId <id>` or `--documentId <id>` | Get a database with its description, property/option schema guidance, item pages, and computed ancestry context |
118
118
  | `list-trashed-content-databases` | | List soft-deleted databases visible in the sidebar Trash surface |
119
119
  | `restore-content-database` | `--databaseId <id>` | Restore a soft-deleted database from the sidebar Trash surface |
120
120
  | `get-content-database-source` | `--databaseId <id>` or `--documentId <id>` | Inspect local/no-source or source-backed status, mappings, row identity, freshness, and change sets |
@@ -138,7 +138,7 @@ cd templates/content && pnpm action <name> [args]
138
138
  | `move-database-item` | `--itemId <id>` or `--documentId <id> --position <number>` | Move a database row page to a new zero-based table position |
139
139
  | `update-content-database-view` | `--databaseId <id> --viewConfig <json>` | Persist database views, sorts, filters, hidden properties, and view settings |
140
140
  | `list-document-properties` | `--documentId <id> [--format json]` | List Notion-style property definitions and values for a document |
141
- | `configure-document-property` | `--documentId <id> [--id <propertyId>] --name <name> --type <type> [--visibility always_show\|hide_when_empty\|always_hide]` | Create or update a property definition |
141
+ | `configure-document-property` | `--documentId <id> [--id <propertyId>] --name <name> --type <type> [--description <text>] [--visibility always_show\|hide_when_empty\|always_hide] [--options <json>]` | Create or update a property definition and its option-level guidance |
142
142
  | `duplicate-document-property` | `--documentId <id> --propertyId <propertyId>` | Duplicate a property definition and its stored values |
143
143
  | `delete-document-property` | `--documentId <id> --propertyId <propertyId>` | Delete a property definition and its stored values |
144
144
  | `set-document-property` | `--documentId <id> --propertyId <propertyId> --value <json>` | Set a document property value (for a `blocks` field, the value is its markdown content) |
@@ -151,6 +151,27 @@ Database views follow Notion-style tab labels. When creating or duplicating
151
151
  views in `viewConfig`, use unique default names (`Table 2`, `SEO copy 2`, etc.)
152
152
  instead of appending several tabs with the same label.
153
153
 
154
+ ### Self-Documenting Descriptions
155
+
156
+ Descriptions are stable semantic guidance, not generated summaries of current
157
+ content. Preserve this distinction when reading or writing them:
158
+
159
+ - A page description explains why the page exists and what belongs there.
160
+ - A database description explains the collection's purpose and inclusion
161
+ boundary. Inline and full-page views of one database share the same
162
+ description.
163
+ - A property description explains what the field means and what value belongs
164
+ there.
165
+ - A select, status, or multi-select option description explains when to choose
166
+ that option.
167
+
168
+ Descriptions are owned; context is inherited. Never copy an ancestor's prose
169
+ into a child description. Focused reads expose a root-to-parent `contextPath`
170
+ so the agent can use ancestor guidance without creating stale duplicates. Read
171
+ the returned descriptions before placing content or setting property values.
172
+ Update a description only when the object's meaning changes, not whenever its
173
+ current content changes.
174
+
154
175
  **`pull-document` is the collab-aware "ingest the final" read** — prefer it over
155
176
  `get-document` for external ingest (another app, an external coding agent over
156
177
  MCP/A2A, an A2A peer). `get-document` returns whatever is in the
@@ -388,6 +409,7 @@ Public documents are reachable at `/p/<id>` once visibility is `public`. Anyone
388
409
  | ------------------------------ | ------------------------------------------------------------------------------ |
389
410
  | "What am I looking at?" | `view-screen` |
390
411
  | "Create a page about X" | `create-document --title "X" --content "# X\n\n..."` |
412
+ | "Explain what belongs here" | `view-screen` to get ID, `update-document --id ... --description "..."` |
391
413
  | "Find my meeting notes" | `search-documents --query "meeting notes"` |
392
414
  | "Update the title of this doc" | `view-screen` to get ID, `update-document --id ... --title "New"` |
393
415
  | "Fix a typo / small edit" | `view-screen` to get ID, `edit-document --id ... --find "old" --replace "new"` |
@@ -49,6 +49,7 @@ import {
49
49
  parsePropertyOptions,
50
50
  serializePropertyOptions,
51
51
  serializePropertyValue,
52
+ type DocumentPropertyOption,
52
53
  type DocumentPropertyOptionColor,
53
54
  } from "../shared/properties.js";
54
55
  import { sanitizeNormalizationFormula } from "../shared/properties.js";
@@ -5193,6 +5194,34 @@ const SOURCE_OPTION_PALETTE: DocumentPropertyOptionColor[] = [
5193
5194
  "red",
5194
5195
  ];
5195
5196
 
5197
+ export function sourcePropertyOptionsForSources(
5198
+ sources: Array<{ id: string; sourceName: string }>,
5199
+ priorOptions: DocumentPropertyOption[],
5200
+ ) {
5201
+ const priorById = new Map(priorOptions.map((option) => [option.id, option]));
5202
+ const retainDescription = (id: string) => {
5203
+ const description = priorById.get(id)?.description;
5204
+ return description ? { description } : {};
5205
+ };
5206
+ return [
5207
+ ...sources.map((source, index) => ({
5208
+ id: source.id,
5209
+ name: source.sourceName,
5210
+ color:
5211
+ priorById.get(source.id)?.color ??
5212
+ SOURCE_OPTION_PALETTE[index % SOURCE_OPTION_PALETTE.length],
5213
+ ...retainDescription(source.id),
5214
+ })),
5215
+ {
5216
+ id: SOURCE_LOCAL_OPTION_ID,
5217
+ name: "Local",
5218
+ color: (priorById.get(SOURCE_LOCAL_OPTION_ID)?.color ??
5219
+ "gray") as DocumentPropertyOptionColor,
5220
+ ...retainDescription(SOURCE_LOCAL_OPTION_ID),
5221
+ },
5222
+ ];
5223
+ }
5224
+
5196
5225
  /**
5197
5226
  * Ensure a "Source" select property exists tagging each row with the collection
5198
5227
  * it belongs to, and (re)set every item's value. Rows with no source binding are
@@ -5233,22 +5262,7 @@ export async function ensureDatabaseSourceProperty(args: {
5233
5262
  // that can't collide with a UUID source id). Resolving a row's tag back to a
5234
5263
  // source is then pure id matching — no source-name hop — so duplicate display
5235
5264
  // names or a collection literally named "Local" can never misroute a row.
5236
- const priorById = new Map(priorOptions.map((option) => [option.id, option]));
5237
- const options = [
5238
- ...sources.map((source, index) => ({
5239
- id: source.id,
5240
- name: source.sourceName,
5241
- color:
5242
- priorById.get(source.id)?.color ??
5243
- SOURCE_OPTION_PALETTE[index % SOURCE_OPTION_PALETTE.length],
5244
- })),
5245
- {
5246
- id: SOURCE_LOCAL_OPTION_ID,
5247
- name: "Local",
5248
- color: (priorById.get(SOURCE_LOCAL_OPTION_ID)?.color ??
5249
- "gray") as DocumentPropertyOptionColor,
5250
- },
5251
- ];
5265
+ const options = sourcePropertyOptionsForSources(sources, priorOptions);
5252
5266
  const optionsJson = serializePropertyOptions({ options });
5253
5267
 
5254
5268
  let propertyId: string;
@@ -1,6 +1,7 @@
1
1
  import { and, asc, eq, inArray, isNull, sql } from "drizzle-orm";
2
2
 
3
3
  import { getDb, schema } from "../server/db/index.js";
4
+ import { getDocumentContextPath } from "../server/lib/document-context.js";
4
5
  import {
5
6
  parseDocumentFavorite,
6
7
  parseDocumentHideFromSearch,
@@ -20,6 +21,7 @@ import {
20
21
  listPropertiesForDatabase,
21
22
  serializeDatabase,
22
23
  } from "./_property-utils.js";
24
+ export { getDocumentContextPath };
23
25
 
24
26
  export const CONTENT_DATABASE_MAX_READ_LIMIT = 5_000;
25
27
 
@@ -131,6 +133,7 @@ function serializeDocument(
131
133
  parentId: doc.parentId,
132
134
  title: doc.title,
133
135
  content: options.includeContent === true ? doc.content : "",
136
+ description: doc.description,
134
137
  icon: doc.icon,
135
138
  position: doc.position,
136
139
  isFavorite: parseDocumentFavorite(doc.isFavorite),
@@ -160,6 +163,14 @@ export async function getContentDatabaseResponse(
160
163
  if (!database || database.deletedAt) {
161
164
  throw new Error(`Database "${databaseId}" not found`);
162
165
  }
166
+ const [databaseDocument] = await db
167
+ .select({
168
+ id: schema.documents.id,
169
+ parentId: schema.documents.parentId,
170
+ description: schema.documents.description,
171
+ })
172
+ .from(schema.documents)
173
+ .where(eq(schema.documents.id, database.documentId));
163
174
 
164
175
  // PURE read: the primary "Content" Blocks field is seeded at create time and
165
176
  // by the one-time startup repair — never here. Reading a database (including a
@@ -272,7 +283,10 @@ export async function getContentDatabaseResponse(
272
283
  const itemsWithOverlay = applyFederatedOverlayValues(federatedItems);
273
284
 
274
285
  return {
275
- database: serializeDatabase(database),
286
+ database: serializeDatabase(database, databaseDocument?.description ?? ""),
287
+ contextPath: databaseDocument
288
+ ? await getDocumentContextPath(databaseDocument)
289
+ : [],
276
290
  properties: await listPropertiesForDatabase(databaseId),
277
291
  items: itemsWithOverlay,
278
292
  source: pagedPrimary,
@@ -154,6 +154,7 @@ function documentFromFolder(folderPath: string, position: number): Document {
154
154
  parentId: folderParentId(folderPath),
155
155
  title: titleFromSegment(basename(folderPath)),
156
156
  content: "",
157
+ description: "",
157
158
  icon: null,
158
159
  position,
159
160
  isFavorite: false,
@@ -189,6 +190,7 @@ function documentFromLocalFile(
189
190
  parentId: parentFolderId(file.path),
190
191
  title: parsed.title,
191
192
  content: parsed.content,
193
+ description: parsed.description ?? "",
192
194
  icon: parsed.icon ?? null,
193
195
  position,
194
196
  isFavorite: parsed.isFavorite ?? false,
@@ -410,6 +412,33 @@ export async function getLocalFileDocument(id: string): Promise<Document> {
410
412
  return documentFromLocalFile(file, file.content, 0);
411
413
  }
412
414
 
415
+ export async function getLocalDocumentContextPath(id: string) {
416
+ const documents = await listLocalFileDocuments();
417
+ const byId = new Map(documents.map((document) => [document.id, document]));
418
+ const current = byId.get(id) ?? (await getLocalFileDocument(id));
419
+ const path: Array<{
420
+ id: string;
421
+ kind: "page" | "database";
422
+ title: string;
423
+ description: string;
424
+ }> = [];
425
+ const seen = new Set([current.id]);
426
+ let parentId = current.parentId;
427
+ while (parentId && !seen.has(parentId)) {
428
+ seen.add(parentId);
429
+ const parent = byId.get(parentId);
430
+ if (!parent) break;
431
+ path.unshift({
432
+ id: parent.id,
433
+ kind: "page",
434
+ title: parent.title,
435
+ description: parent.description ?? "",
436
+ });
437
+ parentId = parent.parentId;
438
+ }
439
+ return path;
440
+ }
441
+
413
442
  function splitFrontmatter(source: string) {
414
443
  const match = source.match(FRONTMATTER_RE);
415
444
  if (!match) return { frontmatter: "", body: source };
@@ -517,12 +546,16 @@ function updateFrontmatterFields(
517
546
  titleChanged: boolean,
518
547
  iconChanged: boolean,
519
548
  favoriteChanged: boolean,
549
+ descriptionChanged: boolean,
520
550
  ) {
521
551
  if (usesDocsNoBookkeepingProfile(file.profile)) {
522
552
  return {
523
553
  ...(titleChanged ? { title: nextTitle || "Untitled" } : {}),
524
554
  ...(iconChanged ? { icon: args.icon ?? null } : {}),
525
555
  ...(favoriteChanged ? { isFavorite: args.isFavorite ?? false } : {}),
556
+ ...(descriptionChanged
557
+ ? { description: args.description?.trim() ?? "" }
558
+ : {}),
526
559
  };
527
560
  }
528
561
 
@@ -531,6 +564,10 @@ function updateFrontmatterFields(
531
564
  icon: args.icon !== undefined ? args.icon : current.icon,
532
565
  isFavorite:
533
566
  args.isFavorite !== undefined ? args.isFavorite : current.isFavorite,
567
+ description:
568
+ args.description !== undefined
569
+ ? args.description.trim()
570
+ : current.description,
534
571
  updatedAt: new Date().toISOString(),
535
572
  };
536
573
  }
@@ -544,6 +581,9 @@ function createFrontmatterFields(
544
581
  return {
545
582
  title,
546
583
  ...(args.icon !== undefined ? { icon: args.icon || null } : {}),
584
+ ...(args.description !== undefined
585
+ ? { description: args.description.trim() }
586
+ : {}),
547
587
  };
548
588
  }
549
589
 
@@ -551,6 +591,7 @@ function createFrontmatterFields(
551
591
  title,
552
592
  icon: args.icon || null,
553
593
  isFavorite: false,
594
+ description: args.description?.trim() ?? "",
554
595
  updatedAt: new Date().toISOString(),
555
596
  };
556
597
  }
@@ -576,8 +617,17 @@ export async function updateLocalFileDocument(
576
617
  const iconChanged = args.icon !== undefined && args.icon !== current.icon;
577
618
  const favoriteChanged =
578
619
  args.isFavorite !== undefined && args.isFavorite !== current.isFavorite;
620
+ const descriptionChanged =
621
+ args.description !== undefined &&
622
+ args.description.trim() !== current.description;
579
623
 
580
- if (!titleChanged && !contentChanged && !iconChanged && !favoriteChanged) {
624
+ if (
625
+ !titleChanged &&
626
+ !contentChanged &&
627
+ !iconChanged &&
628
+ !favoriteChanged &&
629
+ !descriptionChanged
630
+ ) {
581
631
  return current;
582
632
  }
583
633
 
@@ -591,6 +641,7 @@ export async function updateLocalFileDocument(
591
641
  titleChanged,
592
642
  iconChanged,
593
643
  favoriteChanged,
644
+ descriptionChanged,
594
645
  ),
595
646
  nextContent,
596
647
  );
@@ -712,6 +763,7 @@ export async function localContentViewScreenSummary() {
712
763
  id: document.id,
713
764
  parentId: document.parentId,
714
765
  title: document.title,
766
+ description: document.description,
715
767
  source: document.source,
716
768
  })),
717
769
  };
@@ -151,11 +151,15 @@ export async function getDatabaseById(
151
151
  return database ?? null;
152
152
  }
153
153
 
154
- export function serializeDatabase(database: ContentDatabaseRow) {
154
+ export function serializeDatabase(
155
+ database: ContentDatabaseRow,
156
+ description = "",
157
+ ) {
155
158
  return {
156
159
  id: database.id,
157
160
  documentId: database.documentId,
158
161
  title: database.title,
162
+ description,
159
163
  viewConfig: parseDatabaseViewConfig(database.viewConfigJson),
160
164
  createdAt: database.createdAt,
161
165
  updatedAt: database.updatedAt,
@@ -500,6 +504,7 @@ export async function listPropertiesForDatabase(
500
504
  databaseId: definition.databaseId,
501
505
  name: definition.name,
502
506
  type,
507
+ description: definition.description,
503
508
  visibility: normalizePropertyVisibility(definition.visibility),
504
509
  options,
505
510
  position: definition.position,
@@ -568,6 +573,7 @@ function serializePropertyDefinition(
568
573
  databaseId: definition.databaseId,
569
574
  name: definition.name,
570
575
  type,
576
+ description: definition.description,
571
577
  visibility: normalizePropertyVisibility(definition.visibility),
572
578
  options: parsePropertyOptions(definition.optionsJson),
573
579
  position: definition.position,
@@ -36,6 +36,12 @@ export default defineAction({
36
36
  .string()
37
37
  .describe("Document ID used to scope the property workspace"),
38
38
  name: z.string().min(1).describe("Property name"),
39
+ description: z
40
+ .string()
41
+ .optional()
42
+ .describe(
43
+ "Stable guidance describing what this property means and which value belongs here",
44
+ ),
39
45
  type: z.enum(CREATABLE_DOCUMENT_PROPERTY_TYPES).describe("Property type"),
40
46
  visibility: z
41
47
  .enum(DOCUMENT_PROPERTY_VISIBILITIES)
@@ -49,6 +55,7 @@ export default defineAction({
49
55
  id: z.string(),
50
56
  name: z.string(),
51
57
  color: z.string(),
58
+ description: z.string().optional(),
52
59
  }),
53
60
  )
54
61
  .optional(),
@@ -162,6 +169,9 @@ export default defineAction({
162
169
  .update(schema.documentPropertyDefinitions)
163
170
  .set({
164
171
  name,
172
+ ...(args.description === undefined
173
+ ? {}
174
+ : { description: args.description.trim() }),
165
175
  type,
166
176
  visibility:
167
177
  args.visibility === undefined
@@ -196,6 +206,7 @@ export default defineAction({
196
206
  orgId: document.orgId ?? null,
197
207
  databaseId: database.id,
198
208
  name,
209
+ description: args.description?.trim() ?? "",
199
210
  type,
200
211
  visibility: normalizePropertyVisibility(args.visibility),
201
212
  optionsJson,
@@ -28,6 +28,10 @@ const createContentDatabaseSchema = z.object({
28
28
  .nullish()
29
29
  .describe("Parent document for a new database page"),
30
30
  title: z.string().optional().describe("Database title"),
31
+ description: z
32
+ .string()
33
+ .optional()
34
+ .describe("Stable guidance describing what belongs in this database"),
31
35
  });
32
36
 
33
37
  export default defineAction({
@@ -110,6 +114,12 @@ export async function createContentDatabaseRecord(
110
114
  .set({ title, updatedAt: now })
111
115
  .where(eq(schema.documents.id, documentId));
112
116
  }
117
+ if (args.description !== undefined) {
118
+ await db
119
+ .update(schema.documents)
120
+ .set({ description: args.description.trim(), updatedAt: now })
121
+ .where(eq(schema.documents.id, documentId));
122
+ }
113
123
  } else {
114
124
  title = databaseTitleForPage(title);
115
125
  const parentId = args.parentId || null;
@@ -163,6 +173,7 @@ export async function createContentDatabaseRecord(
163
173
  parentId,
164
174
  title,
165
175
  content: "",
176
+ description: args.description?.trim() ?? "",
166
177
  icon: null,
167
178
  position: (maxPos?.max ?? -1) + 1,
168
179
  isFavorite: 0,
@@ -45,6 +45,12 @@ export default defineAction({
45
45
  .describe("Pre-generated document ID (for optimistic UI)"),
46
46
  title: z.string().describe("Document title"),
47
47
  content: z.string().optional().describe("Markdown content"),
48
+ description: z
49
+ .string()
50
+ .optional()
51
+ .describe(
52
+ "Stable guidance describing why this page exists and what belongs in it",
53
+ ),
48
54
  parentId: z.string().nullish().describe("Parent document ID for nesting"),
49
55
  icon: z.string().optional().describe("Emoji icon"),
50
56
  }),
@@ -78,6 +84,7 @@ export default defineAction({
78
84
  const title = args.title;
79
85
 
80
86
  let content = args.content || "";
87
+ const description = args.description?.trim() ?? "";
81
88
  // Strip leading H1 that duplicates the title
82
89
  if (title && content) {
83
90
  const h1Match = content.match(/^#\s+(.+?)(\r?\n|$)/);
@@ -154,6 +161,7 @@ export default defineAction({
154
161
  parentId,
155
162
  title,
156
163
  content,
164
+ description,
157
165
  icon,
158
166
  position,
159
167
  isFavorite: 0,
@@ -202,6 +210,7 @@ export default defineAction({
202
210
  parentId: doc.parentId,
203
211
  title: doc.title,
204
212
  content: doc.content,
213
+ description: doc.description,
205
214
  icon: doc.icon,
206
215
  position: doc.position,
207
216
  isFavorite: parseDocumentFavorite(doc.isFavorite),
@@ -22,10 +22,12 @@ export default defineAction({
22
22
  schema: z.object({
23
23
  hostDocumentId: z.string().describe("Host page document ID"),
24
24
  title: z.string().optional().describe("Database title"),
25
+ description: z.string().optional().describe("Stable database guidance"),
25
26
  }),
26
27
  run: async ({
27
28
  hostDocumentId,
28
29
  title,
30
+ description,
29
31
  }): Promise<CreateInlineDatabaseResponse> => {
30
32
  const db = getDb();
31
33
  const ownerBlockId = createInlineDatabaseBlockId();
@@ -37,6 +39,7 @@ export default defineAction({
37
39
  {
38
40
  parentId: hostDocumentId,
39
41
  title: databaseTitleForPage(title),
42
+ description,
40
43
  },
41
44
  { db: tx },
42
45
  );
@@ -39,6 +39,7 @@ export default defineAction({
39
39
  parentId: doc.parentId,
40
40
  title: doc.title,
41
41
  content: doc.content,
42
+ description: doc.description,
42
43
  icon: doc.icon,
43
44
  position: doc.position,
44
45
  isFavorite: parseDocumentFavorite(doc.isFavorite),
@@ -11,6 +11,7 @@ import type {
11
11
  import {
12
12
  CONTENT_DATABASE_MAX_READ_LIMIT,
13
13
  getContentDatabaseResponse,
14
+ getDocumentContextPath,
14
15
  } from "./_database-utils.js";
15
16
 
16
17
  export default defineAction({
@@ -78,6 +79,17 @@ export default defineAction({
78
79
  };
79
80
  }
80
81
 
81
- return getContentDatabaseResponse(resolvedDatabaseId, { limit, offset });
82
+ const response = await getContentDatabaseResponse(resolvedDatabaseId, {
83
+ limit,
84
+ offset,
85
+ });
86
+ const [document] = await db
87
+ .select()
88
+ .from(schema.documents)
89
+ .where(eq(schema.documents.id, database.documentId));
90
+ return {
91
+ ...response,
92
+ contextPath: document ? await getDocumentContextPath(document) : [],
93
+ };
82
94
  },
83
95
  });
@@ -9,6 +9,7 @@ import {
9
9
  } from "../server/lib/documents.js";
10
10
  import {
11
11
  getDatabaseByDocumentId,
12
+ getDocumentContextPath,
12
13
  getDatabaseItemByDocumentId,
13
14
  isSoftDeletedDatabaseDocument,
14
15
  serializeDatabaseMembership,
@@ -16,6 +17,7 @@ import {
16
17
  import { serializeDocumentSource } from "./_document-source.js";
17
18
  import {
18
19
  getLocalFileDocument,
20
+ getLocalDocumentContextPath,
19
21
  isLocalDocumentId,
20
22
  isContentLocalFileMode,
21
23
  } from "./_local-file-documents.js";
@@ -45,7 +47,11 @@ export default defineAction({
45
47
  if (!args.id) throw new Error("--id is required");
46
48
 
47
49
  if ((await isContentLocalFileMode()) && isLocalDocumentId(args.id)) {
48
- return getLocalFileDocument(args.id);
50
+ const document = await getLocalFileDocument(args.id);
51
+ return {
52
+ ...document,
53
+ contextPath: await getLocalDocumentContextPath(args.id),
54
+ };
49
55
  }
50
56
 
51
57
  const access = await resolveAccess("document", args.id);
@@ -76,6 +82,7 @@ export default defineAction({
76
82
  parentId: doc.parentId,
77
83
  title: doc.title,
78
84
  content: doc.content,
85
+ description: doc.description,
79
86
  icon: doc.icon,
80
87
  position: doc.position,
81
88
  isFavorite: parseDocumentFavorite(doc.isFavorite),
@@ -85,13 +92,16 @@ export default defineAction({
85
92
  accessRole: access.role,
86
93
  canEdit: canEditRole(access.role),
87
94
  canManage: canManageRole(access.role),
88
- database: database ? serializeDatabase(database) : undefined,
95
+ database: database
96
+ ? serializeDatabase(database, doc.description)
97
+ : undefined,
89
98
  databaseMembership: databaseMembership
90
99
  ? serializeDatabaseMembership(databaseMembership)
91
100
  : undefined,
92
101
  createdAt: doc.createdAt,
93
102
  updatedAt: doc.updatedAt,
94
103
  properties: await listPropertiesForDocument(doc),
104
+ contextPath: await getDocumentContextPath(doc),
95
105
  };
96
106
  },
97
107
  link: ({ result }) => {
@@ -282,6 +282,9 @@ export default defineAction({
282
282
  }
283
283
  const titleChanged = file.title !== existing.title;
284
284
  const contentChanged = file.content !== existing.content;
285
+ const descriptionChanged =
286
+ file.description !== undefined &&
287
+ file.description !== existing.description;
285
288
  const iconChanged =
286
289
  file.icon !== undefined && file.icon !== existing.icon;
287
290
  const favoriteChanged =
@@ -299,6 +302,7 @@ export default defineAction({
299
302
  const anyChange =
300
303
  titleChanged ||
301
304
  contentChanged ||
305
+ descriptionChanged ||
302
306
  iconChanged ||
303
307
  favoriteChanged ||
304
308
  discoverabilityChanged ||
@@ -322,6 +326,7 @@ export default defineAction({
322
326
  const updates: Record<string, unknown> = { updatedAt: now };
323
327
  if (titleChanged) updates.title = file.title;
324
328
  if (contentChanged) updates.content = file.content;
329
+ if (descriptionChanged) updates.description = file.description;
325
330
  if (iconChanged) updates.icon = file.icon ?? null;
326
331
  if (favoriteChanged) updates.isFavorite = boolToInt(file.isFavorite);
327
332
  if (discoverabilityChanged) {
@@ -347,6 +352,7 @@ export default defineAction({
347
352
  orgId: currentOrgId,
348
353
  parentId: null,
349
354
  title: file.title,
355
+ description: file.description ?? "",
350
356
  content: file.content,
351
357
  icon: file.icon ?? null,
352
358
  position: file.position ?? index,
@@ -70,6 +70,7 @@ export default defineAction({
70
70
  id: schema.documents.id,
71
71
  parentId: schema.documents.parentId,
72
72
  title: schema.documents.title,
73
+ description: schema.documents.description,
73
74
  contentSnippet: sql<string>`substr(${schema.documents.content}, 1, 400)`,
74
75
  contentLength: sql<number>`length(${schema.documents.content})`,
75
76
  icon: schema.documents.icon,
@@ -282,6 +283,7 @@ export default defineAction({
282
283
  id: d.id,
283
284
  parentId: d.parentId,
284
285
  title: d.title,
286
+ description: d.description,
285
287
  contentPreview: contentPreview(d.contentSnippet),
286
288
  contentLength: Number(d.contentLength) || 0,
287
289
  icon: d.icon,
@@ -299,6 +301,7 @@ export default defineAction({
299
301
  id: database.id,
300
302
  documentId: database.documentId,
301
303
  title: database.title,
304
+ description: d.description,
302
305
  viewConfig: parseDatabaseViewConfig(database.viewConfigJson),
303
306
  createdAt: database.createdAt,
304
307
  updatedAt: database.updatedAt,
@@ -73,6 +73,7 @@ export default defineAction({
73
73
  return {
74
74
  id: doc.id,
75
75
  title: doc.title,
76
+ description: doc.description ?? "",
76
77
  content: formatDocumentContent(doc.content ?? "", format),
77
78
  format,
78
79
  deepLink: buildDeepLink({
@@ -104,6 +105,7 @@ export default defineAction({
104
105
  return {
105
106
  id: doc.id,
106
107
  title: doc.title,
108
+ description: (doc.description as string | null | undefined) ?? "",
107
109
  content,
108
110
  format,
109
111
  deepLink: buildDeepLink({