@ai-matrx/rich-content 0.2.10 → 0.2.16

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 (114) hide show
  1. package/dist/copy/copy-commands.d.ts +42 -0
  2. package/dist/copy/copy-commands.js +119 -0
  3. package/dist/copy/markdown-copy-html.d.ts +3 -0
  4. package/dist/copy/markdown-copy-html.js +8 -0
  5. package/dist/copy/markdown-readable-text.d.ts +4 -0
  6. package/dist/copy/markdown-readable-text.js +62 -0
  7. package/dist/display/chat-markdown/block-registry/BlockComponentRegistry.js +2 -0
  8. package/dist/display/chat-markdown/block-registry/block-dispatch.js +2 -0
  9. package/dist/host/app-bindings.d.ts +30 -0
  10. package/dist/host/app-bindings.js +38 -0
  11. package/dist/host/feedback-subject.d.ts +16 -0
  12. package/dist/host/feedback-subject.js +0 -0
  13. package/dist/host/manual-copy.d.ts +7 -0
  14. package/dist/host/manual-copy.js +11 -0
  15. package/dist/host/registry.d.ts +2 -0
  16. package/dist/host/registry.js +10 -0
  17. package/dist/host/session-knob.d.ts +5 -0
  18. package/dist/host/session-knob.js +7 -0
  19. package/dist/host/types.d.ts +24 -0
  20. package/dist/rich-document/RegistryContextMenu.d.ts +29 -0
  21. package/dist/rich-document/RegistryContextMenu.js +40 -0
  22. package/dist/rich-document/RichDocument.d.ts +78 -0
  23. package/dist/rich-document/RichDocument.js +218 -0
  24. package/dist/rich-document/RichDocumentActionProvider.d.ts +18 -0
  25. package/dist/rich-document/RichDocumentActionProvider.js +18 -0
  26. package/dist/rich-document/RichDocumentActionSurface.d.ts +11 -0
  27. package/dist/rich-document/RichDocumentActionSurface.js +59 -0
  28. package/dist/rich-document/RichDocumentActions.d.ts +12 -0
  29. package/dist/rich-document/RichDocumentActions.js +29 -0
  30. package/dist/rich-document/actions/hostActions.d.ts +5 -0
  31. package/dist/rich-document/actions/hostActions.js +31 -0
  32. package/dist/rich-document/actions/labels.d.ts +9 -0
  33. package/dist/rich-document/actions/labels.js +8 -0
  34. package/dist/rich-document/actions/markdownTable.d.ts +9 -0
  35. package/dist/rich-document/actions/markdownTable.js +39 -0
  36. package/dist/rich-document/actions/provider.d.ts +58 -0
  37. package/dist/rich-document/actions/provider.js +245 -0
  38. package/dist/rich-document/actions/resumePendingAuthAction.d.ts +3 -0
  39. package/dist/rich-document/actions/resumePendingAuthAction.js +31 -0
  40. package/dist/rich-document/actions/sources/index.d.ts +9 -0
  41. package/dist/rich-document/actions/sources/index.js +22 -0
  42. package/dist/rich-document/actions/sources/raw.d.ts +4 -0
  43. package/dist/rich-document/actions/sources/raw.js +35 -0
  44. package/dist/rich-document/actions/useRichDocumentProvider.d.ts +1 -0
  45. package/dist/rich-document/actions/useRichDocumentProvider.js +13 -0
  46. package/dist/rich-document/host-types.d.ts +51 -0
  47. package/dist/rich-document/host-types.js +0 -0
  48. package/dist/rich-document/registrations.d.ts +1 -0
  49. package/dist/rich-document/registrations.js +13 -0
  50. package/dist/rich-document/review/applyTargets.d.ts +16 -0
  51. package/dist/rich-document/review/applyTargets.js +32 -0
  52. package/dist/rich-document/review/proposedEdit.d.ts +22 -0
  53. package/dist/rich-document/review/proposedEdit.js +48 -0
  54. package/dist/rich-document/review/protectedSpans.d.ts +14 -0
  55. package/dist/rich-document/review/protectedSpans.js +61 -0
  56. package/dist/rich-document/runtime/actionSurfacesStore.d.ts +30 -0
  57. package/dist/rich-document/runtime/actionSurfacesStore.js +79 -0
  58. package/dist/rich-document/runtime/contextMenuFor.d.ts +6 -0
  59. package/dist/rich-document/runtime/contextMenuFor.js +7 -0
  60. package/dist/rich-document/runtime/providerBridge.d.ts +20 -0
  61. package/dist/rich-document/runtime/providerBridge.js +23 -0
  62. package/dist/rich-document/runtime/useActionSurfaceProvider.d.ts +33 -0
  63. package/dist/rich-document/runtime/useActionSurfaceProvider.js +214 -0
  64. package/dist/rich-document/search/ContentFindControl.d.ts +8 -0
  65. package/dist/rich-document/search/ContentFindControl.js +46 -0
  66. package/dist/rich-document/search/RenderedFindBar.d.ts +8 -0
  67. package/dist/rich-document/search/RenderedFindBar.js +110 -0
  68. package/dist/rich-document/search/renderedFind.d.ts +1 -0
  69. package/dist/rich-document/search/renderedFind.js +1 -0
  70. package/dist/rich-document/types.d.ts +384 -0
  71. package/dist/rich-document/types.js +0 -0
  72. package/dist/rich-document/variants/ActionBar.d.ts +17 -0
  73. package/dist/rich-document/variants/ActionBar.js +48 -0
  74. package/dist/rich-document/variants/MenuVariant.d.ts +15 -0
  75. package/dist/rich-document/variants/MenuVariant.js +17 -0
  76. package/dist/rich-document/variants/MiniActionBar.d.ts +5 -0
  77. package/dist/rich-document/variants/MiniActionBar.js +11 -0
  78. package/dist/rich-document/variants/RegistryActionList.d.ts +17 -0
  79. package/dist/rich-document/variants/RegistryActionList.js +94 -0
  80. package/dist/rich-document/variants/RegistryActionMenu.d.ts +24 -0
  81. package/dist/rich-document/variants/RegistryActionMenu.js +74 -0
  82. package/dist/rich-document/variants/shared/AlchemyDocumentMenu.d.ts +7 -0
  83. package/dist/rich-document/variants/shared/AlchemyDocumentMenu.js +83 -0
  84. package/dist/rich-document/variants/shared/OpenOneMenuButton.d.ts +9 -0
  85. package/dist/rich-document/variants/shared/OpenOneMenuButton.js +70 -0
  86. package/dist/rich-document/variants/shared/StandaloneOneMenu.d.ts +8 -0
  87. package/dist/rich-document/variants/shared/StandaloneOneMenu.js +32 -0
  88. package/dist/rich-document/variants/shared/categories.d.ts +4 -0
  89. package/dist/rich-document/variants/shared/categories.js +31 -0
  90. package/dist/rich-document/variants/shared/iconMap.d.ts +2 -0
  91. package/dist/rich-document/variants/shared/iconMap.js +73 -0
  92. package/dist/rich-document/variants/shared/menuStructure.d.ts +81 -0
  93. package/dist/rich-document/variants/shared/menuStructure.js +271 -0
  94. package/dist/rich-document/variants/shared/runAction.d.ts +17 -0
  95. package/dist/rich-document/variants/shared/runAction.js +24 -0
  96. package/dist/selection-toolbar/SelectionToolbarFrame.d.ts +64 -0
  97. package/dist/selection-toolbar/SelectionToolbarFrame.js +191 -0
  98. package/dist/selection-toolbar/common-actions.d.ts +2 -0
  99. package/dist/selection-toolbar/common-actions.js +103 -0
  100. package/dist/selection-toolbar/selection-actions.d.ts +126 -0
  101. package/dist/selection-toolbar/selection-actions.js +240 -0
  102. package/dist/selection-toolbar/selection-copy.d.ts +19 -0
  103. package/dist/selection-toolbar/selection-copy.js +98 -0
  104. package/dist/selection-toolbar/selection-shape.d.ts +1 -0
  105. package/dist/selection-toolbar/selection-shape.js +15 -0
  106. package/dist/selection-toolbar/selection-zones.d.ts +90 -0
  107. package/dist/selection-toolbar/selection-zones.js +85 -0
  108. package/dist/selection-toolbar/textarea-caret.d.ts +8 -0
  109. package/dist/selection-toolbar/textarea-caret.js +69 -0
  110. package/dist/utils/alchemy-icon-keys.d.ts +18 -0
  111. package/dist/utils/alchemy-icon-keys.js +29 -0
  112. package/dist/utils/rendered-find.d.ts +9 -0
  113. package/dist/utils/rendered-find.js +68 -0
  114. package/package.json +23 -7
@@ -0,0 +1,245 @@
1
+ import { holdDeliberateIntent } from "../../host/app-bindings.js";
2
+ import {
3
+ computeEligibility,
4
+ createClickTarget
5
+ } from "@ai-matrx/alchemy/actions";
6
+ import { registerAlchemyIcon } from "../../utils/alchemy-icon-keys.js";
7
+ import { MENU_STRUCTURE, menuStructureVersion } from "../variants/shared/menuStructure.js";
8
+ import { resolveActionLabel } from "./labels.js";
9
+ import { ensureRichDocumentRegistrations } from "../registrations.js";
10
+ const RICH_DOCUMENT_PROVIDER_ID = "rich-document";
11
+ const RICH_DOCUMENT_SOURCE_TARGET = "rich_document_source";
12
+ const SOURCE_WRITE_TARGET = {
13
+ name: RICH_DOCUMENT_SOURCE_TARGET,
14
+ label: "This content",
15
+ description: "The record the content came from, changed through its source adapter.",
16
+ valueType: "document",
17
+ mode: "entity"
18
+ };
19
+ function store() {
20
+ const key = /* @__PURE__ */ Symbol.for("ai-matrx.rich-document.actions");
21
+ const g = globalThis;
22
+ let s = g[key];
23
+ if (!s) {
24
+ s = { actions: /* @__PURE__ */ new Map(), listeners: /* @__PURE__ */ new Set() };
25
+ g[key] = s;
26
+ }
27
+ return s;
28
+ }
29
+ function readStore() {
30
+ ensureRichDocumentRegistrations();
31
+ return store();
32
+ }
33
+ function registerAction(action) {
34
+ const { actions: ACTIONS, listeners } = store();
35
+ if (ACTIONS.has(action.id)) {
36
+ if (true) {
37
+ console.warn(
38
+ `[rich-document] action "${action.id}" re-registered (fast refresh) \u2014 replaced.`
39
+ );
40
+ } else {
41
+ throw new DuplicateActionError(action.id, RICH_DOCUMENT_PROVIDER_ID, RICH_DOCUMENT_PROVIDER_ID);
42
+ }
43
+ }
44
+ ACTIONS.set(action.id, action);
45
+ for (const l of [...listeners]) l();
46
+ }
47
+ function getAction(id) {
48
+ return readStore().actions.get(id);
49
+ }
50
+ function getAllActions() {
51
+ return Array.from(readStore().actions.values());
52
+ }
53
+ function richDocumentSectionId(label) {
54
+ return `rd:${label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
55
+ }
56
+ let placementCache = null;
57
+ let placementVersion = -1;
58
+ function placementOf(id) {
59
+ if (placementCache === null || placementVersion !== menuStructureVersion()) {
60
+ placementVersion = menuStructureVersion();
61
+ placementCache = buildPlacement();
62
+ }
63
+ return placementCache.get(id);
64
+ }
65
+ function buildPlacement() {
66
+ const map = /* @__PURE__ */ new Map();
67
+ MENU_STRUCTURE.forEach((section, sectionIndex) => {
68
+ section.actionIds.forEach((id, index) => {
69
+ const order = sectionIndex * 100 + index;
70
+ if (section.submenu === null || section.inline) {
71
+ map.set(id, { order });
72
+ } else {
73
+ const icon = section.icon ? registerAlchemyIcon(section.icon) : void 0;
74
+ map.set(id, {
75
+ order,
76
+ ...section.layoutCategory ? { category: section.layoutCategory } : {},
77
+ section: {
78
+ id: richDocumentSectionId(section.submenu),
79
+ label: section.submenu,
80
+ ...icon ? { icon } : {},
81
+ ...section.target ? { kind: "target" } : {}
82
+ }
83
+ });
84
+ }
85
+ });
86
+ });
87
+ return map;
88
+ }
89
+ const MENU_ALIASES = {
90
+ "regenerate-latest": "regenerate-response"
91
+ };
92
+ function hostOf(target) {
93
+ const host = target.host;
94
+ if (!host) return null;
95
+ if (host.kind === "rich-document") return host;
96
+ return host.richDocument?.kind === "rich-document" ? host.richDocument : null;
97
+ }
98
+ function richDocumentTargetHost(ctx, options = {}) {
99
+ return { kind: "rich-document", ctx, getCtx: options.getCtx ?? (() => ctx), extra: options.extra ?? [] };
100
+ }
101
+ function richDocumentClickTarget(ctx, options = {}) {
102
+ const readOnly = Boolean(ctx.source.readOnly);
103
+ const host = {
104
+ kind: "rich-document",
105
+ ctx,
106
+ getCtx: options.getCtx ?? (() => ctx),
107
+ extra: options.extra ?? []
108
+ };
109
+ return createClickTarget({
110
+ readOnly,
111
+ writable: readOnly ? [] : [SOURCE_WRITE_TARGET],
112
+ excludedActionIds: options.exclude ?? [],
113
+ organizationId: ctx.organizationId,
114
+ auth: { authenticated: ctx.isAuthenticated, admin: ctx.isAdmin, creator: ctx.isCreator },
115
+ payloadKinds: ["markdown"],
116
+ host
117
+ });
118
+ }
119
+ function eligibleFor(rd, target) {
120
+ const host = hostOf(target);
121
+ if (!host) return { status: "absent" };
122
+ const ctx = host.ctx;
123
+ if (rd.supportedSources !== "*" && !rd.supportedSources.includes(ctx.source.type)) {
124
+ return { status: "absent" };
125
+ }
126
+ if (rd.visible && !rd.visible(ctx)) return { status: "absent" };
127
+ const disabled = rd.disabled?.(ctx);
128
+ if (disabled === true || typeof disabled === "object" && disabled !== null) {
129
+ return { status: "absent" };
130
+ }
131
+ return { status: "available" };
132
+ }
133
+ const converted = /* @__PURE__ */ new WeakMap();
134
+ function toAlchemyAction(rd) {
135
+ const cached = converted.get(rd);
136
+ if (cached) return cached;
137
+ const place = placementOf(rd.id);
138
+ const ctxOf = (t) => hostOf(t)?.ctx;
139
+ const action = {
140
+ id: rd.id,
141
+ provider: RICH_DOCUMENT_PROVIDER_ID,
142
+ label: (t) => {
143
+ const ctx = ctxOf(t);
144
+ return ctx ? resolveActionLabel(rd.label, ctx) : typeof rd.label === "string" ? rd.label : rd.id;
145
+ },
146
+ icon: registerAlchemyIcon(rd.icon),
147
+ ...rd.iconColor ? { iconTone: rd.iconColor } : {},
148
+ ...rd.stateIcon ? {
149
+ stateIcon: (t) => {
150
+ const ctx = ctxOf(t);
151
+ const live = ctx ? rd.stateIcon?.(ctx) : null;
152
+ return live ? { icon: registerAlchemyIcon(live.icon), ...live.spin ? { spin: true } : {} } : null;
153
+ }
154
+ } : {},
155
+ category: place?.category ?? rd.category,
156
+ order: place?.order ?? 1e4 + (rd.order ?? 0),
157
+ placement: rd.renderSlot ?? "overflow",
158
+ ...place?.section ? { section: place.section } : {},
159
+ ...rd.requiresAuth ? { requiresAuth: true } : {},
160
+ ...rd.writesSource ? { writes: [RICH_DOCUMENT_SOURCE_TARGET] } : {},
161
+ ...rd.preserveSelection ? { preserveSelection: true } : {},
162
+ ...rd.active ? {
163
+ pressed: (t) => {
164
+ const ctx = ctxOf(t);
165
+ return ctx ? Boolean(rd.active?.(ctx)) : false;
166
+ }
167
+ } : {},
168
+ ...rd.subscribe ? {
169
+ subscribe: (onChange, t) => {
170
+ const ctx = ctxOf(t);
171
+ if (!ctx || !rd.subscribe) return () => void 0;
172
+ let subscribing = true;
173
+ const off = rd.subscribe(() => {
174
+ if (!subscribing) onChange();
175
+ }, ctx);
176
+ subscribing = false;
177
+ return off;
178
+ }
179
+ } : {},
180
+ eligible: (t) => eligibleFor(rd, t),
181
+ run: async (t) => {
182
+ const host = hostOf(t);
183
+ if (!host) return;
184
+ await holdDeliberateIntent(() => rd.run(host.getCtx()));
185
+ }
186
+ };
187
+ converted.set(rd, action);
188
+ return action;
189
+ }
190
+ function candidates(target) {
191
+ const host = hostOf(target);
192
+ if (!host) return [];
193
+ const ACTIONS = readStore().actions;
194
+ const all = [...ACTIONS.values(), ...host.extra.filter((a) => !ACTIONS.has(a.id))];
195
+ const present = new Set(all.map((a) => a.id));
196
+ return all.filter((a) => {
197
+ const canonical = MENU_ALIASES[a.id];
198
+ return !(canonical && present.has(canonical));
199
+ });
200
+ }
201
+ const REGISTERED = /* @__PURE__ */ new WeakSet();
202
+ function ensureRichDocumentProvider(registry) {
203
+ if (REGISTERED.has(registry)) return;
204
+ REGISTERED.add(registry);
205
+ registry.register(richDocumentActionProvider);
206
+ }
207
+ const richDocumentActionProvider = {
208
+ id: RICH_DOCUMENT_PROVIDER_ID,
209
+ tier: "T0",
210
+ declaredIds: () => [...readStore().actions.keys()],
211
+ actions: (target) => candidates(target).map(toAlchemyAction)
212
+ };
213
+ function subscribeRichDocumentActions(listener) {
214
+ const { listeners } = store();
215
+ listeners.add(listener);
216
+ return () => {
217
+ listeners.delete(listener);
218
+ };
219
+ }
220
+ function resolveActions(ctx, options) {
221
+ const target = richDocumentClickTarget(ctx, {
222
+ exclude: options?.exclude ?? [],
223
+ extra: options?.extra ?? []
224
+ });
225
+ return candidates(target).filter((rd) => computeEligibility(toAlchemyAction(rd), target, { authResumable: false }).status === "available").sort((a, b) => {
226
+ if (a.category !== b.category) return a.category.localeCompare(b.category);
227
+ return (a.order ?? 0) - (b.order ?? 0);
228
+ });
229
+ }
230
+ export {
231
+ RICH_DOCUMENT_PROVIDER_ID,
232
+ RICH_DOCUMENT_SOURCE_TARGET,
233
+ SOURCE_WRITE_TARGET,
234
+ ensureRichDocumentProvider,
235
+ getAction,
236
+ getAllActions,
237
+ registerAction,
238
+ resolveActions,
239
+ richDocumentActionProvider,
240
+ richDocumentClickTarget,
241
+ richDocumentSectionId,
242
+ richDocumentTargetHost,
243
+ subscribeRichDocumentActions,
244
+ toAlchemyAction
245
+ };
@@ -0,0 +1,3 @@
1
+ import type { RichDocumentActionContext } from "../types.js";
2
+ /** Replay a stashed action when the context's content matches. Never throws. */
3
+ export declare function resumePendingAuthAction(ctx: RichDocumentActionContext): void;
@@ -0,0 +1,31 @@
1
+ import { getAction } from "./provider.js";
2
+ import { PENDING_ACTION_KEY } from "./labels.js";
3
+ function resumePendingAuthAction(ctx) {
4
+ if (!ctx.isAuthenticated) return;
5
+ let pending = null;
6
+ try {
7
+ const raw = sessionStorage.getItem(PENDING_ACTION_KEY);
8
+ if (!raw) return;
9
+ pending = JSON.parse(raw);
10
+ } catch {
11
+ return;
12
+ }
13
+ if (!pending || pending.savedContent !== ctx.content) return;
14
+ try {
15
+ sessionStorage.removeItem(PENDING_ACTION_KEY);
16
+ } catch {
17
+ }
18
+ const action = typeof pending.action === "string" ? getAction(pending.action) : void 0;
19
+ if (!action) {
20
+ console.error(
21
+ `[RichDocument] post-auth resume: no registered action "${String(pending.action)}" \u2014 the pending request was dropped.`
22
+ );
23
+ return;
24
+ }
25
+ void Promise.resolve(action.run(ctx)).catch((err) => {
26
+ console.error(`[RichDocument] post-auth resume of ${action.id} threw`, err);
27
+ });
28
+ }
29
+ export {
30
+ resumePendingAuthAction
31
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * ContentSourceType → ContentSourceAdapter. The engine ships `raw`; the host registers the rest
3
+ * (matrx-frontend: chat-message, note, prompt-result, artifact, scraper-result, working-document)
4
+ * through `registerSourceAdapter`, on import of its registration module.
5
+ */
6
+ import type { ContentSourceAdapter, ContentSourceType } from "../../types.js";
7
+ /** Register (or replace) the adapter for one source type. */
8
+ export declare function registerSourceAdapter(sourceType: ContentSourceType, adapter: ContentSourceAdapter): void;
9
+ export declare function getSourceAdapter(sourceType: ContentSourceType): ContentSourceAdapter;
@@ -0,0 +1,22 @@
1
+ import { rawAdapter } from "./raw.js";
2
+ import { ensureRichDocumentRegistrations } from "../../registrations.js";
3
+ const KEY = /* @__PURE__ */ Symbol.for("ai-matrx.rich-content.rich-document.source-adapters");
4
+ function adapters() {
5
+ const g = globalThis;
6
+ return g[KEY] ??= /* @__PURE__ */ new Map([["raw", rawAdapter]]);
7
+ }
8
+ function registerSourceAdapter(sourceType, adapter) {
9
+ adapters().set(sourceType, adapter);
10
+ }
11
+ function getSourceAdapter(sourceType) {
12
+ ensureRichDocumentRegistrations();
13
+ const adapter = adapters().get(sourceType);
14
+ if (!adapter) {
15
+ throw new Error(`No source adapter registered for type: ${sourceType}`);
16
+ }
17
+ return adapter;
18
+ }
19
+ export {
20
+ getSourceAdapter,
21
+ registerSourceAdapter
22
+ };
@@ -0,0 +1,4 @@
1
+ import type { ContentSourceAdapter } from "../../types.js";
2
+ declare function shortHash(input: string): string;
3
+ export declare const rawAdapter: ContentSourceAdapter;
4
+ export { shortHash };
@@ -0,0 +1,35 @@
1
+ const LONG_INPUT = 4096;
2
+ const recent = /* @__PURE__ */ new Map();
3
+ function shortHash(input) {
4
+ if (input.length >= LONG_INPUT) {
5
+ const hit = recent.get(input);
6
+ if (hit !== void 0) return hit;
7
+ const computed = fnv1a(input);
8
+ if (recent.size >= 8) recent.delete(recent.keys().next().value);
9
+ recent.set(input, computed);
10
+ return computed;
11
+ }
12
+ return fnv1a(input);
13
+ }
14
+ function fnv1a(input) {
15
+ let hash = 2166136261;
16
+ for (let i = 0; i < input.length; i++) {
17
+ hash ^= input.charCodeAt(i);
18
+ hash = hash * 16777619 >>> 0;
19
+ }
20
+ return hash.toString(16).padStart(8, "0");
21
+ }
22
+ const rawAdapter = {
23
+ instanceKeyPrefix: (source) => {
24
+ if (source.type !== "raw") {
25
+ throw new Error(
26
+ `rawAdapter received non-raw source: ${source.type}`
27
+ );
28
+ }
29
+ return "raw";
30
+ }
31
+ };
32
+ export {
33
+ rawAdapter,
34
+ shortHash
35
+ };
@@ -0,0 +1 @@
1
+ export declare function useRichDocumentProvider(): void;
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ import { useAlchemyActions } from "@ai-matrx/alchemy/react/host";
4
+ import { ensureRichDocumentProvider } from "./provider.js";
5
+ function useRichDocumentProvider() {
6
+ const { registry } = useAlchemyActions();
7
+ React.useLayoutEffect(() => {
8
+ ensureRichDocumentProvider(registry);
9
+ }, [registry]);
10
+ }
11
+ export {
12
+ useRichDocumentProvider
13
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The host's own types the rich-document contract carries (its store's dispatch and state, its
3
+ * note record, its application scope). The host fills them in by augmentation — matrx-frontend:
4
+ *
5
+ * declare module "@ai-matrx/rich-content/rich-document/host-types" {
6
+ * interface RichDocumentHostTypes { dispatch: AppDispatch; state: RootState; note: Note; ... }
7
+ * }
8
+ *
9
+ * Unaugmented, each is a structural default.
10
+ */
11
+ export interface RichDocumentHostTypes {
12
+ }
13
+ type HostType<K extends string, Default> = RichDocumentHostTypes extends Record<K, infer T> ? T : Default;
14
+ export type AppDispatch = HostType<"dispatch", (action: any) => any>;
15
+ export type RootState = HostType<"state", Record<string, unknown>>;
16
+ export type ApplicationScope = HostType<"applicationScope", Record<string, unknown>>;
17
+ export type Note = HostType<"note", {
18
+ id: string;
19
+ content?: string | null;
20
+ label?: string | null;
21
+ [key: string]: unknown;
22
+ }>;
23
+ export type NoteSaveReceipt = HostType<"noteSaveReceipt", {
24
+ [key: string]: unknown;
25
+ }>;
26
+ /** The saved record a content source names (matrx-frontend: annotations/record-of-source). */
27
+ export interface AnnotationRecord {
28
+ token: string;
29
+ id: string;
30
+ title?: string;
31
+ contentVersion?: number;
32
+ href?: string;
33
+ /** A chat answer's conversation. */
34
+ conversationId?: string;
35
+ }
36
+ /** The reading set's record + body (matrx-frontend: annotations/types `AnnotationSource`). */
37
+ export interface AnnotationSource {
38
+ token: string;
39
+ id: string;
40
+ title: string;
41
+ body: string;
42
+ contentVersion: number;
43
+ href?: string;
44
+ conversationId?: string;
45
+ }
46
+ /** A server-processed block the full renderer accepts (matrx-frontend: EnhancedChatMarkdown). */
47
+ export type ServerProcessedBlock = HostType<"serverProcessedBlock", {
48
+ type: string;
49
+ [key: string]: unknown;
50
+ }>;
51
+ export {};
File without changes
@@ -0,0 +1 @@
1
+ export declare function ensureRichDocumentRegistrations(): void;
@@ -0,0 +1,13 @@
1
+ import { hostCapability } from "../host/registry.js";
2
+ const ENSURED = /* @__PURE__ */ Symbol.for("ai-matrx.rich-content.rich-document.registrations-ensured");
3
+ function ensureRichDocumentRegistrations() {
4
+ const g = globalThis;
5
+ if (g[ENSURED]) return;
6
+ const ensure = hostCapability("ensureRichDocumentRegistrations");
7
+ if (!ensure) return;
8
+ g[ENSURED] = true;
9
+ ensure();
10
+ }
11
+ export {
12
+ ensureRichDocumentRegistrations
13
+ };
@@ -0,0 +1,16 @@
1
+ import type { RichDocumentActionContext } from "../types.js";
2
+ interface ApplyTarget {
3
+ ctx: RichDocumentActionContext;
4
+ /** What the source is called, for the action's label. */
5
+ label: string;
6
+ }
7
+ /** True when an answer about this text could be applied back to it. */
8
+ export declare function canApplyBack(ctx: RichDocumentActionContext): boolean;
9
+ export declare function registerApplyTarget(ctx: RichDocumentActionContext, label: string): string;
10
+ export declare function bindConversationToApplyTarget(conversationId: string, targetId: string): void;
11
+ export declare function applyTargetForConversation(conversationId: string | null | undefined): {
12
+ id: string;
13
+ label: string;
14
+ } | null;
15
+ export declare function getApplyTarget(id: string | null | undefined): ApplyTarget | null;
16
+ export {};
@@ -0,0 +1,32 @@
1
+ const targets = /* @__PURE__ */ new Map();
2
+ const byConversation = /* @__PURE__ */ new Map();
3
+ let seq = 0;
4
+ const APPLYABLE_SOURCES = /* @__PURE__ */ new Set(["note", "chat-message", "working-document"]);
5
+ function canApplyBack(ctx) {
6
+ return APPLYABLE_SOURCES.has(ctx.source.type) && ctx.source.readOnly !== true;
7
+ }
8
+ function registerApplyTarget(ctx, label) {
9
+ seq += 1;
10
+ const id = `apply-target-${seq}`;
11
+ targets.set(id, { ctx, label });
12
+ return id;
13
+ }
14
+ function bindConversationToApplyTarget(conversationId, targetId) {
15
+ if (targets.has(targetId)) byConversation.set(conversationId, targetId);
16
+ }
17
+ function applyTargetForConversation(conversationId) {
18
+ if (!conversationId) return null;
19
+ const id = byConversation.get(conversationId);
20
+ const target = id ? targets.get(id) : void 0;
21
+ return id && target ? { id, label: target.label } : null;
22
+ }
23
+ function getApplyTarget(id) {
24
+ return id ? targets.get(id) ?? null : null;
25
+ }
26
+ export {
27
+ applyTargetForConversation,
28
+ bindConversationToApplyTarget,
29
+ canApplyBack,
30
+ getApplyTarget,
31
+ registerApplyTarget
32
+ };
@@ -0,0 +1,22 @@
1
+ import { type SourceEdit, type SpliceResult } from "@ai-matrx/content-ir/source";
2
+ export interface ProposedEdit {
3
+ /** The one block-aligned edit, or null when nothing differs. */
4
+ edit: SourceEdit | null;
5
+ /** Blocks of the original that stay byte-identical. */
6
+ keptBlocks: number;
7
+ /** Blocks of the original the edit replaces. */
8
+ replacedBlocks: number;
9
+ }
10
+ export declare function reduceToBlockEdit(original: string, proposed: string): ProposedEdit;
11
+ /**
12
+ * The text to persist for a proposal: `original` with only the changed run
13
+ * spliced in. Throws `SourceSpliceError` when the splice would disturb an
14
+ * island it did not name — the caller surfaces that, never forces it.
15
+ */
16
+ export declare function spliceProposal(original: string, proposed: string): SpliceResult | null;
17
+ /**
18
+ * A splice refusal in words a person reads — never offsets or function names
19
+ * ("edit [0, 776) changes … island at [141, 233); islands change only through
20
+ * islandEdit()" was shown verbatim, verify-RC-B5 round 3 F7).
21
+ */
22
+ export declare function explainSpliceRefusal(error: unknown): string;
@@ -0,0 +1,48 @@
1
+ import {
2
+ SourceSpliceError,
3
+ spliceSave,
4
+ tokenizeSource
5
+ } from "@ai-matrx/content-ir/source";
6
+ function reduceToBlockEdit(original, proposed) {
7
+ const a = tokenizeSource(original);
8
+ const b = tokenizeSource(proposed);
9
+ let head = 0;
10
+ while (head < a.length && head < b.length && a[head].raw === b[head].raw) {
11
+ head += 1;
12
+ }
13
+ if (head === a.length && head === b.length) {
14
+ return { edit: null, keptBlocks: a.length, replacedBlocks: 0 };
15
+ }
16
+ let tail = 0;
17
+ while (tail < a.length - head && tail < b.length - head && a[a.length - 1 - tail].raw === b[b.length - 1 - tail].raw) {
18
+ tail += 1;
19
+ }
20
+ const oldFirst = head;
21
+ const oldLast = a.length - 1 - tail;
22
+ const start = oldFirst < a.length ? a[oldFirst].start : original.length;
23
+ const end = oldLast >= oldFirst ? a[oldLast].end : start;
24
+ const newStart = head < b.length ? b[head].start : proposed.length;
25
+ const newEnd = b.length - 1 - tail >= head ? b[b.length - 1 - tail].end : newStart;
26
+ return {
27
+ edit: { start, end, text: proposed.slice(newStart, newEnd) },
28
+ keptBlocks: head + tail,
29
+ replacedBlocks: Math.max(0, oldLast - oldFirst + 1)
30
+ };
31
+ }
32
+ function spliceProposal(original, proposed) {
33
+ const { edit } = reduceToBlockEdit(original, proposed);
34
+ if (!edit) return null;
35
+ return spliceSave(original, [edit]);
36
+ }
37
+ function explainSpliceRefusal(error) {
38
+ const code = error instanceof SourceSpliceError ? error.code : null;
39
+ if (code === "island_edit" || code === "integrity") {
40
+ return "This change would alter protected content \u2014 code, math, a table, a section or hidden reasoning \u2014 which is only changed in its own editor.";
41
+ }
42
+ return "This change could not be placed exactly into the saved text.";
43
+ }
44
+ export {
45
+ explainSpliceRefusal,
46
+ reduceToBlockEdit,
47
+ spliceProposal
48
+ };
@@ -0,0 +1,14 @@
1
+ export interface MaskedText {
2
+ /** What the agent receives. */
3
+ text: string;
4
+ /** Original bytes of each span; marker `⟦Pn⟧` stands for spans[n-1]. */
5
+ spans: string[];
6
+ }
7
+ export declare function maskProtectedSpans(text: string): MaskedText;
8
+ export type UnmaskResult = {
9
+ text: string;
10
+ } | {
11
+ error: string;
12
+ };
13
+ /** Put every protected span back, byte-for-byte — or say which ones the agent broke. */
14
+ export declare function unmaskProtectedSpans(result: string, spans: readonly string[]): UnmaskResult;
@@ -0,0 +1,61 @@
1
+ import { listIslands, tokenizeSource } from "@ai-matrx/content-ir/source";
2
+ const ESCAPES = (
3
+ // eslint-disable-next-line no-control-regex
4
+ /\u001b\[[0-?]*[ -/]*[@-~]|\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)|\u001b[@-Z\\-_]|[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g
5
+ );
6
+ const MARKER = /⟦P(\d+)⟧/g;
7
+ function maskProtectedSpans(text) {
8
+ if (MARKER.test(text)) {
9
+ MARKER.lastIndex = 0;
10
+ return { text, spans: [] };
11
+ }
12
+ MARKER.lastIndex = 0;
13
+ const ranges = [];
14
+ for (const m of text.matchAll(ESCAPES)) {
15
+ ranges.push({ start: m.index ?? 0, end: (m.index ?? 0) + m[0].length });
16
+ }
17
+ for (const island of listIslands(tokenizeSource(text))) {
18
+ if (island.inline || island.islandType === "json") {
19
+ ranges.push({ start: island.start, end: island.end });
20
+ }
21
+ }
22
+ ranges.sort((a, b) => a.start - b.start || b.end - a.end);
23
+ const kept = [];
24
+ for (const r of ranges) {
25
+ const last = kept[kept.length - 1];
26
+ if (last && r.start < last.end) continue;
27
+ kept.push(r);
28
+ }
29
+ if (kept.length === 0) return { text, spans: [] };
30
+ const spans = [];
31
+ let out = "";
32
+ let at = 0;
33
+ for (const r of kept) {
34
+ out += text.slice(at, r.start);
35
+ spans.push(text.slice(r.start, r.end));
36
+ out += `\u27E6P${spans.length}\u27E7`;
37
+ at = r.end;
38
+ }
39
+ out += text.slice(at);
40
+ return { text: out, spans };
41
+ }
42
+ function unmaskProtectedSpans(result, spans) {
43
+ if (spans.length === 0) return { text: result };
44
+ const seen = /* @__PURE__ */ new Map();
45
+ for (const m of result.matchAll(MARKER)) {
46
+ const n = Number(m[1]);
47
+ seen.set(n, (seen.get(n) ?? 0) + 1);
48
+ }
49
+ const broken = spans.map((_, i) => i + 1).filter((n) => seen.get(n) !== 1);
50
+ const invented = [...seen.keys()].filter((n) => n < 1 || n > spans.length);
51
+ if (broken.length > 0 || invented.length > 0) {
52
+ return {
53
+ error: "The agent's result dropped or changed protected text (terminal codes, variables or similar), so it cannot be applied. Run it again, or make the change in the editor."
54
+ };
55
+ }
56
+ return { text: result.replace(MARKER, (_, n) => spans[Number(n) - 1]) };
57
+ }
58
+ export {
59
+ maskProtectedSpans,
60
+ unmaskProtectedSpans
61
+ };
@@ -0,0 +1,30 @@
1
+ import type { RichDocumentActionSpec, RichDocumentSurfaceRegistration } from "../types.js";
2
+ export interface ActionSurfacesState {
3
+ /** surfaceId → ordered stack of providers (oldest first, newest at end). */
4
+ bySurfaceId: Record<string, RichDocumentSurfaceRegistration[]>;
5
+ }
6
+ /** Push a provider onto a surface's stack; a re-registration by providerId updates in place. */
7
+ export declare function registerProvider(payload: {
8
+ surfaceId: string;
9
+ registration: RichDocumentSurfaceRegistration;
10
+ }): void;
11
+ /** Update the action-spec snapshot for an already-registered provider. */
12
+ export declare function updateProviderSpecs(payload: {
13
+ surfaceId: string;
14
+ providerId: string;
15
+ computedActionSpecs: RichDocumentActionSpec[];
16
+ contentSourceId: string;
17
+ }): void;
18
+ /** Remove a provider by id; the remaining order is preserved. */
19
+ export declare function unregisterProvider(payload: {
20
+ surfaceId: string;
21
+ providerId: string;
22
+ }): void;
23
+ /** Test/maintenance: clear everything. */
24
+ export declare function clearAll(): void;
25
+ export declare function getActionSurfacesState(): ActionSurfacesState;
26
+ export declare function selectTopProvider(state: ActionSurfacesState, surfaceId: string): RichDocumentSurfaceRegistration | null;
27
+ export declare function selectProviderStack(state: ActionSurfacesState, surfaceId: string): RichDocumentSurfaceRegistration[];
28
+ export declare function selectSurfaceActionSpecs(state: ActionSurfacesState, surfaceId: string): RichDocumentActionSpec[];
29
+ /** The top-of-stack provider for a surface, live. */
30
+ export declare function useTopProvider(surfaceId: string): RichDocumentSurfaceRegistration | null;