@agent-native/core 0.101.5 → 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 (149) hide show
  1. package/README.md +1 -1
  2. package/corpus/README.md +1 -1
  3. package/corpus/core/CHANGELOG.md +12 -0
  4. package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +2 -2
  5. package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +2 -2
  6. package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +2 -2
  7. package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +2 -2
  8. package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +2 -2
  9. package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +2 -2
  10. package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +2 -2
  11. package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +2 -2
  12. package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +2 -2
  13. package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +2 -2
  14. package/corpus/core/docs/content/pure-agent-apps.mdx +58 -14
  15. package/corpus/core/package.json +1 -1
  16. package/corpus/core/src/a2a/artifact-response.ts +255 -12
  17. package/corpus/core/src/agent/thread-data-builder.ts +84 -11
  18. package/corpus/core/src/integrations/a2a-continuation-processor.ts +99 -7
  19. package/corpus/core/src/integrations/adapters/discord.ts +3 -1
  20. package/corpus/core/src/integrations/adapters/email.ts +4 -1
  21. package/corpus/core/src/integrations/adapters/google-docs.ts +4 -1
  22. package/corpus/core/src/integrations/adapters/microsoft-teams.ts +3 -1
  23. package/corpus/core/src/integrations/adapters/slack.ts +38 -9
  24. package/corpus/core/src/integrations/adapters/telegram.ts +32 -10
  25. package/corpus/core/src/integrations/adapters/whatsapp.ts +5 -1
  26. package/corpus/core/src/integrations/index.ts +1 -0
  27. package/corpus/core/src/integrations/types.ts +9 -2
  28. package/corpus/core/src/integrations/webhook-handler.ts +69 -45
  29. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +1 -0
  30. package/corpus/templates/analytics/AGENTS.md +6 -0
  31. package/corpus/templates/analytics/README.md +1 -1
  32. package/corpus/templates/analytics/actions/data-source-status.ts +19 -1
  33. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +5 -1
  34. package/corpus/templates/analytics/app/routes/_index.tsx +2 -2
  35. package/corpus/templates/analytics/changelog/2026-07-14-analytics-chat-guides-you-to-connect-a-data-source-with-a-di.md +6 -0
  36. package/corpus/templates/analytics/changelog/2026-07-14-analytics-landing-pages-now-clearly-position-the-app-as-an-o.md +6 -0
  37. package/corpus/templates/analytics/changelog/2026-07-15-daily-dashboard-emails-now-include-the-complete-dashboard-sc.md +6 -0
  38. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +8 -1
  39. package/corpus/templates/analytics/server/lib/dashboard-report.ts +64 -12
  40. package/corpus/templates/analytics/server/lib/real-data-actions.ts +15 -3
  41. package/corpus/templates/analytics/server/plugins/agent-chat.ts +364 -4
  42. package/corpus/templates/clips/app/components/dictate/vocabulary-section.tsx +1 -0
  43. package/corpus/templates/clips/app/components/library/folder-tree.tsx +12 -9
  44. package/corpus/templates/clips/app/components/library/library-layout.tsx +19 -16
  45. package/corpus/templates/clips/app/routes/_app.spaces._index.tsx +13 -9
  46. package/corpus/templates/clips/changelog/2026-07-15-folders-and-sidebar-links-are-clickable-again-immediately-af.md +6 -0
  47. package/corpus/templates/clips/changelog/2026-07-15-hovering-a-truncated-folder-name-now-shows-the-full-name.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-07-15-space-creation-controls-are-now-shown-only-to-organization-a.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-15-vocabulary-terms-can-now-be-deleted-from-the-dictation-dicti.md +6 -0
  50. package/corpus/templates/clips/desktop/src/styles.css +23 -1
  51. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +49 -7
  52. package/corpus/templates/clips/server/lib/recordings.ts +5 -2
  53. package/corpus/templates/content/.agents/skills/content/SKILL.md +31 -0
  54. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +35 -21
  55. package/corpus/templates/content/AGENTS.md +29 -7
  56. package/corpus/templates/content/actions/_database-source-utils.ts +30 -16
  57. package/corpus/templates/content/actions/_database-utils.ts +15 -1
  58. package/corpus/templates/content/actions/_local-file-documents.ts +53 -1
  59. package/corpus/templates/content/actions/_property-utils.ts +7 -1
  60. package/corpus/templates/content/actions/configure-document-property.ts +11 -0
  61. package/corpus/templates/content/actions/create-content-database.ts +11 -0
  62. package/corpus/templates/content/actions/create-document.ts +9 -0
  63. package/corpus/templates/content/actions/create-inline-content-database.ts +3 -0
  64. package/corpus/templates/content/actions/export-content-source.ts +1 -0
  65. package/corpus/templates/content/actions/get-content-database.ts +13 -1
  66. package/corpus/templates/content/actions/get-document.ts +12 -2
  67. package/corpus/templates/content/actions/import-content-source.ts +6 -0
  68. package/corpus/templates/content/actions/list-documents.ts +3 -0
  69. package/corpus/templates/content/actions/pull-document.ts +2 -0
  70. package/corpus/templates/content/actions/search-documents.ts +5 -1
  71. package/corpus/templates/content/actions/update-document.ts +16 -1
  72. package/corpus/templates/content/actions/view-screen.ts +15 -2
  73. package/corpus/templates/content/app/components/editor/DescriptionField.tsx +121 -0
  74. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +14 -0
  75. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +223 -34
  76. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +56 -8
  77. package/corpus/templates/content/app/i18n/zh-TW.ts +5 -0
  78. package/corpus/templates/content/app/i18n-data.ts +52 -0
  79. package/corpus/templates/content/changelog/2026-07-14-pages-and-databases-can-describe-themselves-with-guidance-fo.md +6 -0
  80. package/corpus/templates/content/changelog/2026-07-14-slack-follow-ups-now-retain-created-content-identity-and-per.md +6 -0
  81. package/corpus/templates/content/server/db/schema.ts +4 -0
  82. package/corpus/templates/content/server/lib/document-context.ts +76 -0
  83. package/corpus/templates/content/server/lib/public-documents.ts +3 -0
  84. package/corpus/templates/content/server/plugins/db.ts +6 -0
  85. package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +5 -0
  86. package/corpus/templates/content/shared/api.ts +17 -0
  87. package/corpus/templates/content/shared/content-source.ts +7 -0
  88. package/corpus/templates/content/shared/properties.ts +2 -0
  89. package/dist/a2a/artifact-response.d.ts +15 -0
  90. package/dist/a2a/artifact-response.d.ts.map +1 -1
  91. package/dist/a2a/artifact-response.js +204 -5
  92. package/dist/a2a/artifact-response.js.map +1 -1
  93. package/dist/agent/thread-data-builder.d.ts +6 -0
  94. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  95. package/dist/agent/thread-data-builder.js +71 -8
  96. package/dist/agent/thread-data-builder.js.map +1 -1
  97. package/dist/collab/struct-routes.d.ts +1 -1
  98. package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
  99. package/dist/integrations/a2a-continuation-processor.js +72 -6
  100. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  101. package/dist/integrations/adapters/discord.d.ts.map +1 -1
  102. package/dist/integrations/adapters/discord.js +1 -0
  103. package/dist/integrations/adapters/discord.js.map +1 -1
  104. package/dist/integrations/adapters/email.d.ts.map +1 -1
  105. package/dist/integrations/adapters/email.js +2 -0
  106. package/dist/integrations/adapters/email.js.map +1 -1
  107. package/dist/integrations/adapters/google-docs.d.ts.map +1 -1
  108. package/dist/integrations/adapters/google-docs.js +2 -0
  109. package/dist/integrations/adapters/google-docs.js.map +1 -1
  110. package/dist/integrations/adapters/microsoft-teams.d.ts.map +1 -1
  111. package/dist/integrations/adapters/microsoft-teams.js +1 -0
  112. package/dist/integrations/adapters/microsoft-teams.js.map +1 -1
  113. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  114. package/dist/integrations/adapters/slack.js +21 -6
  115. package/dist/integrations/adapters/slack.js.map +1 -1
  116. package/dist/integrations/adapters/telegram.d.ts.map +1 -1
  117. package/dist/integrations/adapters/telegram.js +11 -2
  118. package/dist/integrations/adapters/telegram.js.map +1 -1
  119. package/dist/integrations/adapters/whatsapp.d.ts.map +1 -1
  120. package/dist/integrations/adapters/whatsapp.js +3 -0
  121. package/dist/integrations/adapters/whatsapp.js.map +1 -1
  122. package/dist/integrations/index.d.ts +1 -1
  123. package/dist/integrations/index.d.ts.map +1 -1
  124. package/dist/integrations/index.js.map +1 -1
  125. package/dist/integrations/types.d.ts +8 -2
  126. package/dist/integrations/types.d.ts.map +1 -1
  127. package/dist/integrations/types.js.map +1 -1
  128. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  129. package/dist/integrations/webhook-handler.js +40 -43
  130. package/dist/integrations/webhook-handler.js.map +1 -1
  131. package/dist/progress/routes.d.ts +1 -1
  132. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  133. package/dist/resources/handlers.d.ts +2 -2
  134. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
  135. package/dist/server/agent-chat/action-filters-a2a.js +1 -0
  136. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
  137. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  138. package/docs/content/locales/ar-SA/pure-agent-apps.mdx +2 -2
  139. package/docs/content/locales/de-DE/pure-agent-apps.mdx +2 -2
  140. package/docs/content/locales/es-ES/pure-agent-apps.mdx +2 -2
  141. package/docs/content/locales/fr-FR/pure-agent-apps.mdx +2 -2
  142. package/docs/content/locales/hi-IN/pure-agent-apps.mdx +2 -2
  143. package/docs/content/locales/ja-JP/pure-agent-apps.mdx +2 -2
  144. package/docs/content/locales/ko-KR/pure-agent-apps.mdx +2 -2
  145. package/docs/content/locales/pt-BR/pure-agent-apps.mdx +2 -2
  146. package/docs/content/locales/zh-CN/pure-agent-apps.mdx +2 -2
  147. package/docs/content/locales/zh-TW/pure-agent-apps.mdx +2 -2
  148. package/docs/content/pure-agent-apps.mdx +58 -14
  149. package/package.json +3 -3
@@ -495,14 +495,22 @@ mod macos {
495
495
  let mut out = Vec::new();
496
496
  for segment in state.as_iter() {
497
497
  let text = segment.to_string();
498
- if is_speech(&text) && segment.no_speech_probability() < MAX_NO_SPEECH_PROBABILITY {
499
- // whisper timestamps are in centiseconds → ms.
500
- out.push((
501
- segment.start_timestamp() * 10,
502
- segment.end_timestamp() * 10,
503
- text,
504
- ));
498
+ if !is_speech(&text) || segment.no_speech_probability() >= MAX_NO_SPEECH_PROBABILITY {
499
+ continue;
500
+ }
501
+ // Low average token confidence means whisper was guessing —
502
+ // typically a mis-detected-language hallucination that reads
503
+ // fluently but scores poorly. Drop it.
504
+ let confidence = segment_confidence(&segment);
505
+ if confidence < MIN_AVG_TOKEN_PROBABILITY {
506
+ continue;
505
507
  }
508
+ // whisper timestamps are in centiseconds → ms.
509
+ out.push((
510
+ segment.start_timestamp() * 10,
511
+ segment.end_timestamp() * 10,
512
+ text,
513
+ ));
506
514
  }
507
515
  out
508
516
  }
@@ -530,6 +538,40 @@ mod macos {
530
538
  const VOICE_RMS_THRESHOLD: f32 = 0.006;
531
539
  /// A second, model-level gate for ambient/no-speech Whisper segments.
532
540
  const MAX_NO_SPEECH_PROBABILITY: f32 = 0.72;
541
+ /// Minimum average per-token probability for a segment to count as real
542
+ /// speech. On noisy/near-silent audio whisper mis-detects the language and
543
+ /// decodes fluent-looking gibberish in another language — but those tokens
544
+ /// are low-confidence under the hood. Dropping segments below this cutoff
545
+ /// removes that wrong-language garbage while keeping genuine multilingual
546
+ /// speech (which decodes with high confidence).
547
+ const MIN_AVG_TOKEN_PROBABILITY: f32 = 0.55;
548
+
549
+ /// Average the model's per-token probability across a segment. Returns 0.0
550
+ /// for an empty segment so it is treated as low-confidence. Special tokens
551
+ /// (timestamps, `[_BEG_]`, …) render as `[_…]` and are skipped so they
552
+ /// don't skew the average toward the text tokens we actually care about.
553
+ fn segment_confidence(segment: &whisper_rs::WhisperSegment<'_>) -> f32 {
554
+ let mut sum = 0.0f32;
555
+ let mut count = 0u32;
556
+ for i in 0..segment.n_tokens() {
557
+ let Some(token) = segment.get_token(i) else {
558
+ continue;
559
+ };
560
+ let is_special = token
561
+ .to_str_lossy()
562
+ .map(|t| t.starts_with("[_"))
563
+ .unwrap_or(false);
564
+ if is_special {
565
+ continue;
566
+ }
567
+ sum += token.token_probability();
568
+ count += 1;
569
+ }
570
+ if count == 0 {
571
+ return 0.0;
572
+ }
573
+ sum / count as f32
574
+ }
533
575
 
534
576
  fn partial_inference_due(
535
577
  emit_partials: bool,
@@ -6,7 +6,7 @@ import {
6
6
  getRequestOrgId,
7
7
  } from "@agent-native/core/server/request-context";
8
8
  import { and, desc, eq, sql } from "drizzle-orm";
9
- import type { H3Event } from "h3";
9
+ import { HTTPError, type H3Event } from "h3";
10
10
 
11
11
  import { getDb, schema } from "../db/index.js";
12
12
 
@@ -120,7 +120,10 @@ export async function requireOrganizationAccess(
120
120
  const email = getCurrentOwnerEmail();
121
121
  const role = await getOrganizationRoleForEmail(resolvedOrganizationId, email);
122
122
  if (!role || !organizationRoleAllowed(role, allowedRoles)) {
123
- throw new Error("Organization not found or access denied");
123
+ throw new HTTPError({
124
+ statusCode: 403,
125
+ statusMessage: "Organization not found or access denied",
126
+ });
124
127
  }
125
128
  return { organizationId: resolvedOrganizationId, email, role };
126
129
  }
@@ -101,6 +101,37 @@ inferences for confirmation, then submit once. When required information is
101
101
  missing, keep the clarification in the originating thread and retain earlier
102
102
  answers as context.
103
103
 
104
+ ### Slack Follow-ups And Corrections
105
+
106
+ A follow-up in an existing Slack thread is not automatically a new intake. Read
107
+ the thread context and inspect the prior Content artifact identity first,
108
+ including any returned document ID or `/page/<id>` path and the canonical
109
+ database row when available. Then choose the operation that matches the user's
110
+ intent:
111
+
112
+ - **Update** the same document for corrections, refinements, status changes, or
113
+ renames that still describe the same request. A rename changes the title, not
114
+ the artifact identity: preserve the stable Content document ID and page path.
115
+ - **Add** new details to the same document when the follow-up extends the
116
+ original request without replacing it.
117
+ - **Supersede** only when the user intends a replacement artifact or distinct
118
+ successor and the workspace's schema or instructions define how that
119
+ relationship is recorded. Preserve a concrete link to the prior artifact.
120
+ - **Create** only when the follow-up is genuinely a separate request or the user
121
+ explicitly asks for a new artifact. Do not blindly submit another row merely
122
+ because a new Slack message arrived.
123
+
124
+ Apply people fields from verified identity and intent, not from convenient
125
+ guesswork:
126
+
127
+ - When the database has a `Requester` field, default it to the verified Slack
128
+ sender unless the user explicitly identifies a different requester.
129
+ - A named doer such as "for Apoorva" maps to `Assignee` when that field exists.
130
+ Naming an assignee never changes or replaces `Requester`.
131
+ - Resolve named people to the database's accepted person identity before
132
+ writing. If a named person cannot be resolved unambiguously, clarify in the
133
+ originating Slack thread; never omit, downgrade, or silently drop the person.
134
+
104
135
  ## Local File Mode
105
136
 
106
137
  Install into an existing repo with:
@@ -7,7 +7,10 @@ description: >-
7
7
 
8
8
  # Document Editing
9
9
 
10
- Documents are stored in the SQL database via Drizzle ORM. Each document has a title, markdown content, optional parent (for nesting), and a position for ordering.
10
+ Documents are stored in the SQL database via Drizzle ORM. Each document has a
11
+ title, stable description, markdown content, optional parent (for nesting), and
12
+ a position for ordering. The description explains why the page exists and what
13
+ belongs there; it is not a changing synopsis of the current body.
11
14
 
12
15
  ## Scripts
13
16
 
@@ -48,6 +51,7 @@ Create a new document.
48
51
  pnpm action create-document --title "Meeting Notes" --content "# Meeting Notes\n\nAttendees: ..."
49
52
  pnpm action create-document --title "Sub Page" --parentId parent123
50
53
  pnpm action create-document --title "My Page" --icon "📝"
54
+ pnpm action create-document --title "Research" --description "Evidence and source notes that support the current project"
51
55
  ```
52
56
 
53
57
  ### edit-document
@@ -73,6 +77,7 @@ Update an existing document. Use for **full rewrites or new content**, not for s
73
77
  pnpm action update-document --id abc123 --title "New Title"
74
78
  pnpm action update-document --id abc123 --content "# Updated Content\n\nNew text here"
75
79
  pnpm action update-document --id abc123 --title "New Title" --content "New content"
80
+ pnpm action update-document --id abc123 --description "Stable guidance for what belongs on this page"
76
81
  ```
77
82
 
78
83
  ### delete-document
@@ -123,17 +128,18 @@ Always run this after any document modification to update the sidebar.
123
128
 
124
129
  ## Document Schema
125
130
 
126
- | Column | Type | Description |
127
- | ------------ | ------- | --------------------------------------- |
128
- | `id` | text | Primary key (12-char hex string) |
129
- | `parent_id` | text | Parent document ID (null for root) |
130
- | `title` | text | Document title (default: "Untitled") |
131
- | `content` | text | Markdown content |
132
- | `icon` | text | Emoji icon (optional) |
133
- | `position` | integer | Sort order within parent (0-based) |
134
- | `is_favorite`| integer | Whether document is favorited (0 or 1) |
135
- | `created_at` | text | ISO timestamp |
136
- | `updated_at` | text | ISO timestamp |
131
+ | Column | Type | Description |
132
+ | ------------- | ------- | -------------------------------------- |
133
+ | `id` | text | Primary key (12-char hex string) |
134
+ | `parent_id` | text | Parent document ID (null for root) |
135
+ | `title` | text | Document title (default: "Untitled") |
136
+ | `description` | text | Stable semantic guidance for the page |
137
+ | `content` | text | Markdown content |
138
+ | `icon` | text | Emoji icon (optional) |
139
+ | `position` | integer | Sort order within parent (0-based) |
140
+ | `is_favorite` | integer | Whether document is favorited (0 or 1) |
141
+ | `created_at` | text | ISO timestamp |
142
+ | `updated_at` | text | ISO timestamp |
137
143
 
138
144
  ## Content Format
139
145
 
@@ -142,21 +148,29 @@ Documents use **markdown** for content. The editor renders markdown in real time
142
148
  ## Parent-Child Hierarchy
143
149
 
144
150
  Documents form a tree via `parent_id`:
151
+
145
152
  - Root documents have `parent_id = null`
146
153
  - Child documents reference their parent's `id`
147
154
  - Deleting a parent recursively deletes all children
148
155
  - Position determines ordering within the same parent
149
156
 
157
+ Descriptions are owned; context is inherited. `get-document` and `view-screen`
158
+ return the focused page's own description plus a computed root-to-parent
159
+ `contextPath`. Use that path to understand where the page lives, but never copy
160
+ ancestor descriptions into the child. Database, property, and option
161
+ descriptions narrow the guidance further when working with structured values.
162
+
150
163
  ## Common Tasks
151
164
 
152
- | User says | What to do |
153
- | ----------------------------- | ---------------------------------------------------------------- |
154
- | "Create a page about X" | `create-document --title "X" --content "# X\n\n..."` |
155
- | "Find my meeting notes" | `search-documents --query "meeting notes"` |
156
- | "Fix a typo / edit a line" | `view-screen` to get ID, then `edit-document --id ... --find "old" --replace "new"` |
157
- | "Rewrite this document" | `view-screen` to get ID, then `update-document --id ... --content ...` |
158
- | "Delete this page" | `view-screen` to get ID, then `delete-document --id ...` |
159
- | "Add a sub-page" | `create-document --title "Sub" --parentId <parentId>` |
160
- | "Show me the document tree" | `list-documents` |
165
+ | User says | What to do |
166
+ | ---------------------------- | ----------------------------------------------------------------------------------- |
167
+ | "Create a page about X" | `create-document --title "X" --content "# X\n\n..."` |
168
+ | "Describe what belongs here" | `update-document --id ... --description "..."` |
169
+ | "Find my meeting notes" | `search-documents --query "meeting notes"` |
170
+ | "Fix a typo / edit a line" | `view-screen` to get ID, then `edit-document --id ... --find "old" --replace "new"` |
171
+ | "Rewrite this document" | `view-screen` to get ID, then `update-document --id ... --content ...` |
172
+ | "Delete this page" | `view-screen` to get ID, then `delete-document --id ...` |
173
+ | "Add a sub-page" | `create-document --title "Sub" --parentId <parentId>` |
174
+ | "Show me the document tree" | `list-documents` |
161
175
 
162
176
  Always run `refresh-list` after any create, update, or delete operation.
@@ -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,