@ai-matrx/chat 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/agent-copy/AlchemySurfaceBridge.d.ts +1 -1
  3. package/dist/agent-copy/AlchemySurfaceBridge.js +1 -1
  4. package/dist/agent-copy/buildAgentPayload.d.ts +1 -1
  5. package/dist/agent-copy/buildAgentPayload.js +1 -1
  6. package/dist/agent-copy/page-capture/pageCapture.d.ts +1 -1
  7. package/dist/agents/components/inputs/smart-input/AgentTextarea.d.ts +1 -2
  8. package/dist/agents/components/inputs/smart-input/AgentTextarea.js +0 -12
  9. package/dist/agents/components/inputs/smart-input/InputActionButtons.js +2 -2
  10. package/dist/agents/components/inputs/smart-input/SmartAgentInputStacked.js +3 -2
  11. package/dist/agents/components/inputs/smart-input/SmartInputFileDropTarget.d.ts +8 -1
  12. package/dist/agents/components/inputs/smart-input/SmartInputFileDropTarget.js +3 -1
  13. package/dist/agents/components/inputs/smart-input/composer/ComposerModeSwitch.js +10 -15
  14. package/dist/surfaces/manifests/_baseline.manifest.d.ts +59 -0
  15. package/dist/surfaces/manifests/_baseline.manifest.js +56 -1
  16. package/dist/surfaces/runtime/SurfaceRuntimeContext.d.ts +1 -1
  17. package/dist/surfaces/runtime/custom-field-targets.d.ts +6 -6
  18. package/dist/surfaces/runtime/custom-field-targets.js +7 -21
  19. package/dist/surfaces/runtime/surface-feedback.d.ts +7 -9
  20. package/dist/surfaces/runtime/surface-feedback.js +8 -21
  21. package/dist/surfaces/runtime/surface-writeback.d.ts +5 -2
  22. package/dist/surfaces/runtime/surface-writeback.js +171 -135
  23. package/dist/surfaces/runtime/window-forms.d.ts +2 -2
  24. package/dist/surfaces/runtime/window-forms.js +3 -9
  25. package/dist/utils/connectors/marks.d.ts +2 -2
  26. package/dist/utils/connectors/marks.js +2 -9
  27. package/package.json +6 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @ai-matrx/chat
2
2
 
3
+ ## 0.2.1
4
+ ### Re-sync with matrx-frontend's packages/chat (P27)
5
+
6
+ - Carries every frontend-side change made to `packages/chat` after the 0.2.0 copy: a pasted image
7
+ attaches once (react-dropzone owns paste; the hand-rolled paste handler is gone; the host's
8
+ no-paste setting reaches the drop target), the Chat/Work/Advanced switch is the design system's
9
+ `SegmentedControl`, the send tooltip uses `isMacLike` and the Connect chip the one GitHub glyph,
10
+ surfaces import `@ai-matrx/alchemy/operate` (ALC-16/17: platform write targets, Save to
11
+ document/workbook through the one door, `receipt.result` carries what the page's handler made).
12
+ - Fixes the copy's stale host suites (they import `testing/fake-db`, as the frontend moved them).
13
+ - Consumer action: none.
14
+
3
15
  ## 0.2.0
4
16
  ### The full chat system moves in (P27, PACKAGE-INDEPENDENCE)
5
17
 
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
- import { type SurfaceHandle } from "@ai-matrx/kit/content-transfer";
2
+ import { type SurfaceHandle } from "@ai-matrx/alchemy/operate";
3
3
  import type { SurfaceScopePayload } from "../surfaces/types.js";
4
4
  /**
5
5
  * Creates a handle for this exact React mount. Hook-only runtime registrations
@@ -3,7 +3,7 @@ import { jsx } from "react/jsx-runtime";
3
3
  import { useEffect, useId, useRef } from "react";
4
4
  import {
5
5
  createSurfaceTransferHandle
6
- } from "@ai-matrx/kit/content-transfer";
6
+ } from "@ai-matrx/alchemy/operate";
7
7
  import { ContentTransferSurfaceProvider } from "@ai-matrx/design-system/content-transfer";
8
8
  import { getSurfaceManifest } from "../host/ui-slots.js";
9
9
  function useAlchemySurfaceHandle(surfaceName, getScope) {
@@ -1,2 +1,2 @@
1
1
  /** Compatibility import path; agent envelope serialization is package-owned. */
2
- export { buildAgentPayload, fenceJsonBlock, type AgentPayloadInput, } from "@ai-matrx/kit/content-transfer";
2
+ export { buildAgentPayload, fenceJsonBlock, type AgentPayloadInput, } from "@ai-matrx/alchemy/operate";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  buildAgentPayload,
3
3
  fenceJsonBlock
4
- } from "@ai-matrx/kit/content-transfer";
4
+ } from "@ai-matrx/alchemy/operate";
5
5
  export {
6
6
  buildAgentPayload,
7
7
  fenceJsonBlock
@@ -14,7 +14,7 @@
14
14
  * sections, and the recent requests with their timings. Pure: no React, no
15
15
  * network — the suite drives it directly.
16
16
  */
17
- import type { AgentPayloadInput } from "@ai-matrx/kit/content-transfer";
17
+ import type { AgentPayloadInput } from "@ai-matrx/alchemy/operate";
18
18
  import type { AlchemyGroomerConfig } from "@ai-matrx/design-system/content-transfer";
19
19
  export type PageCaptureKind = "table-page" | "record" | "dialog" | "admin-page";
20
20
  /** A chosen thing, by name and id. Either may be null (not chosen / not loaded). */
@@ -21,7 +21,6 @@ interface AgentTextareaProps {
21
21
  singleRow?: boolean;
22
22
  uploadRoot?: string;
23
23
  uploadPath?: string;
24
- enablePasteImages?: boolean;
25
24
  surfaceKey?: string;
26
25
  /**
27
26
  * The key an unsent draft is also kept under, so it survives a reload of a
@@ -62,5 +61,5 @@ interface AgentTextareaProps {
62
61
  /** With `composerType`: each text line is one 32px composer row (splash · page). */
63
62
  composerRows?: boolean;
64
63
  }
65
- export declare function AgentTextarea({ conversationId, compact, singleRow, uploadRoot, uploadPath, enablePasteImages, surfaceKey, draftAlias, disableSend, initiallyExpanded, autoFocus, showExpandToggle, placeholder, minHeightPx, flush, composerType, composerRows, maxHeightPx, textMenu, }: AgentTextareaProps): React.JSX.Element;
64
+ export declare function AgentTextarea({ conversationId, compact, singleRow, uploadRoot, uploadPath, surfaceKey, draftAlias, disableSend, initiallyExpanded, autoFocus, showExpandToggle, placeholder, minHeightPx, flush, composerType, composerRows, maxHeightPx, textMenu, }: AgentTextareaProps): React.JSX.Element;
66
65
  export {};
@@ -23,14 +23,12 @@ import {
23
23
  selectComposerHasSomethingToSend,
24
24
  selectIsExecuting
25
25
  } from "../../../redux/execution-system/selectors/aggregate.selectors.js";
26
- import { useClipboardPaste } from "../../../../host/ui-slots.js";
27
26
  import { focusUnlessTypingElsewhere } from "../../../../utils/dom/focus-guard.js";
28
27
  import {
29
28
  composerKeyIntent,
30
29
  intentTakesTheKey
31
30
  } from "../../../../ui/composer/composerSubmit.js";
32
31
  import { readVerticalChrome, snapToLineGrid } from "./textarea-line-grid.js";
33
- import { usePasteImageResource } from "../resources/usePasteImageResource.js";
34
32
  import { useInstanceInputUndoRedo } from "../../../hooks/useInstanceInputUndoRedo.js";
35
33
  import { ComposerDraftNotice } from "./ComposerDraftNotice.js";
36
34
  import { ComposerToolsNotice } from "./ComposerToolsNotice.js";
@@ -48,7 +46,6 @@ function AgentTextarea({
48
46
  singleRow = false,
49
47
  uploadRoot = "userContent",
50
48
  uploadPath = "agent-attachments",
51
- enablePasteImages = true,
52
49
  surfaceKey,
53
50
  draftAlias,
54
51
  disableSend = false,
@@ -128,15 +125,6 @@ function AgentTextarea({
128
125
  if (disableSend) return;
129
126
  dispatch(interruptAndSend({ conversationId, surfaceKey }));
130
127
  }, [disableSend, conversationId, surfaceKey, dispatch]);
131
- const handlePasteImage = usePasteImageResource(conversationId, {
132
- uploadRoot,
133
- uploadPath
134
- });
135
- useClipboardPaste({
136
- textareaRef,
137
- onPasteImage: handlePasteImage,
138
- disabled: !enablePasteImages
139
- });
140
128
  const handleTextChange = useCallback(
141
129
  (value) => {
142
130
  dispatch(
@@ -36,6 +36,7 @@ import {
36
36
  smartExecute,
37
37
  cancelExecution
38
38
  } from "../../../redux/execution-system/thunks/smart-execute.thunk.js";
39
+ import { isMacLike } from "../../../hooks/useAgentUndoRedo.js";
39
40
  import { MicDeviceMenu } from "../../../../host/ui-slots.js";
40
41
  import { Button, SplitButton } from "@ai-matrx/design-system/controls";
41
42
  import { cn } from "@ai-matrx/design-system";
@@ -212,7 +213,6 @@ function ComposerSendSlot({
212
213
  );
213
214
  return /* @__PURE__ */ jsx(Fragment, { children: children(hasSomethingToSend) });
214
215
  }
215
- const IS_MAC = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
216
216
  function Keys({ keys }) {
217
217
  return /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center gap-0.5", children: keys.map((key) => /* @__PURE__ */ jsx(
218
218
  "kbd",
@@ -230,7 +230,7 @@ function ComposerSendButton({
230
230
  disabled,
231
231
  onSend
232
232
  }) {
233
- const mod = IS_MAC ? "\u2318" : "Ctrl";
233
+ const mod = isMacLike() ? "\u2318" : "Ctrl";
234
234
  const rows = isExecuting ? [
235
235
  { label: "Queue for when it finishes", keys: submitOnEnter ? ["\u21B5"] : [mod, "\u21B5"] },
236
236
  ...submitOnEnter ? [{ label: "Steer in now", keys: [mod, "\u21B5"] }] : [],
@@ -77,6 +77,7 @@ function SmartAgentInputStacked({
77
77
  conversationId,
78
78
  uploadRoot,
79
79
  uploadPath,
80
+ pasteFiles: enablePasteImages,
80
81
  className: cn(
81
82
  "relative flex w-full min-h-0 flex-col gap-1.5 border border-border bg-card transition-colors focus-within:border-foreground/25",
82
83
  "[&_[data-variable-row]]:px-1.5 [&_[data-variable-heading]]:px-1.5 [&>input[type=file]]:!hidden",
@@ -152,7 +153,6 @@ function SmartAgentInputStacked({
152
153
  compact: isCompact,
153
154
  uploadRoot,
154
155
  uploadPath,
155
- enablePasteImages,
156
156
  surfaceKey,
157
157
  disableSend: sendBlocked,
158
158
  initiallyExpanded: expandRequestKey > 0,
@@ -192,6 +192,7 @@ function SmartAgentInputStacked({
192
192
  conversationId,
193
193
  uploadRoot,
194
194
  uploadPath,
195
+ pasteFiles: enablePasteImages,
195
196
  className: cn(
196
197
  "relative flex w-full min-h-0 flex-col gap-1 rounded-[18px] border p-1.5",
197
198
  "border-glass-edge bg-glass shadow-glass backdrop-blur-glass backdrop-saturate-glass transition-[border-color,background-color,box-shadow]",
@@ -230,7 +231,6 @@ function SmartAgentInputStacked({
230
231
  compact: true,
231
232
  uploadRoot,
232
233
  uploadPath,
233
- enablePasteImages,
234
234
  surfaceKey,
235
235
  disableSend: sendBlocked,
236
236
  autoFocus: false,
@@ -280,6 +280,7 @@ function SmartAgentInputStacked({
280
280
  conversationId,
281
281
  uploadRoot,
282
282
  uploadPath,
283
+ pasteFiles: enablePasteImages,
283
284
  className: cardClassName,
284
285
  children: [
285
286
  /* @__PURE__ */ jsx(
@@ -2,7 +2,14 @@ interface SmartInputFileDropTargetProps extends Omit<React.ComponentPropsWithout
2
2
  conversationId: string;
3
3
  uploadRoot?: string;
4
4
  uploadPath?: string;
5
+ /**
6
+ * Pasted files (a screenshot, Cmd/Ctrl+V of a copied file) attach through
7
+ * the same upload as a drop. react-dropzone OWNS paste-to-upload (v20+,
8
+ * on by default); nothing else in the input may also catch the paste, or
9
+ * every pasted image attaches twice (2026-10-06). False = paste stays text.
10
+ */
11
+ pasteFiles?: boolean;
5
12
  children: React.ReactNode;
6
13
  }
7
- export declare function SmartInputFileDropTarget({ conversationId, uploadRoot, uploadPath, className, children, ...rootProps }: SmartInputFileDropTargetProps): import("react").JSX.Element;
14
+ export declare function SmartInputFileDropTarget({ conversationId, uploadRoot, uploadPath, pasteFiles, className, children, ...rootProps }: SmartInputFileDropTargetProps): import("react").JSX.Element;
8
15
  export {};
@@ -8,6 +8,7 @@ function SmartInputFileDropTarget({
8
8
  conversationId,
9
9
  uploadRoot,
10
10
  uploadPath,
11
+ pasteFiles = true,
11
12
  className,
12
13
  children,
13
14
  ...rootProps
@@ -19,7 +20,8 @@ function SmartInputFileDropTarget({
19
20
  const { getRootProps, getInputProps, isDragActive } = useDropzone({
20
21
  onDrop: (files) => void uploadResources(files),
21
22
  noClick: true,
22
- noKeyboard: true
23
+ noKeyboard: true,
24
+ noPaste: !pasteFiles
23
25
  });
24
26
  return /* @__PURE__ */ jsxs(
25
27
  "div",
@@ -16,7 +16,7 @@ import {
16
16
  COMPOSER_MODE_LABELS
17
17
  } from "./composer-types.js";
18
18
  import { useComposerMode } from "./useComposerMode.js";
19
- import { Button } from "@ai-matrx/design-system/controls";
19
+ import { SegmentedControl } from "@ai-matrx/design-system/controls";
20
20
  function ComposerModeSwitch({
21
21
  initialMode,
22
22
  size = "bar",
@@ -30,20 +30,15 @@ function ComposerModeSwitch({
30
30
  const fit = useCenterControlFit(cellRef, [fullRef, triggerRef], mode);
31
31
  const measured = useSyncExternalStore(noopSubscribe, () => true, () => false);
32
32
  const renderSegments = (interactive) => /* @__PURE__ */ jsx(
33
- "div",
33
+ SegmentedControl,
34
34
  {
35
- role: interactive ? "tablist" : void 0,
36
- "aria-label": interactive ? "Composer mode" : void 0,
37
- className: cn(
38
- "shrink-0 items-center gap-0.5 rounded-lg bg-muted p-0.5",
39
- panel || !interactive ? "inline-flex rounded-lg" : measured ? "inline-flex rounded-[10px]" : "hidden rounded-[10px] sm:inline-flex",
40
- !interactive && "w-max max-w-none",
41
- className
42
- ),
43
- children: COMPOSER_MODES.map((value) => {
44
- const on = value === mode;
45
- return /* @__PURE__ */ jsx(Button, { variant: "quiet", pressed: !!on, role: interactive ? "tab" : void 0, "aria-selected": interactive ? on : void 0, "aria-pressed": interactive ? void 0 : on, tabIndex: interactive ? void 0 : -1, onClick: interactive ? () => setMode(value) : void 0, children: COMPOSER_MODE_LABELS[value] }, value);
46
- })
35
+ "aria-label": "Composer mode",
36
+ value: mode,
37
+ onValueChange: (value) => {
38
+ if (interactive) setMode(value);
39
+ },
40
+ data: COMPOSER_MODES.map((value) => ({ value, label: COMPOSER_MODE_LABELS[value] })),
41
+ className: cn("shrink-0", !interactive && "w-max max-w-none", className)
47
42
  }
48
43
  );
49
44
  if (panel) return renderSegments(true);
@@ -61,7 +56,7 @@ function ComposerModeSwitch({
61
56
  className: "relative flex w-full min-w-0 justify-center",
62
57
  "data-route-nav-inflow": fit.inflow ? "" : void 0,
63
58
  children: [
64
- /* @__PURE__ */ jsxs("div", { "aria-hidden": true, className: "pointer-events-none invisible absolute left-0 top-0", children: [
59
+ /* @__PURE__ */ jsxs("div", { "aria-hidden": true, inert: true, className: "pointer-events-none invisible absolute left-0 top-0", children: [
65
60
  /* @__PURE__ */ jsx("div", { ref: fullRef, className: "w-max max-w-none", children: renderSegments(false) }),
66
61
  /* @__PURE__ */ jsx("span", { ref: triggerRef, "data-route-nav-min": true, className: cn(triggerClass, "w-max max-w-none"), children: triggerFace })
67
62
  ] }),
@@ -150,6 +150,65 @@ export declare const PLATFORM_CONTEXT_VALUES: {
150
150
  readonly sortOrder: 9997;
151
151
  };
152
152
  };
153
+ /**
154
+ * THE BASELINE SURFACE — where the platform's own write targets are declared
155
+ * (Matrx Alchemy ALC-17). Every surface inherits it: the one write door reads
156
+ * it as the root ancestor of every surface (`surfaceWriteDeclarations` in
157
+ * `runtime/surface-writeback.ts`), so a write to a platform target from ANY
158
+ * surface resolves here, and the platform's headless handlers are registered
159
+ * on this name. It is not a page and is never mounted.
160
+ */
161
+ export declare const BASELINE_SURFACE_NAME = "matrx-platform/baseline";
162
+ /** `surface_feedback` value bounds — the description and the check read the same numbers. */
163
+ export declare const SURFACE_FEEDBACK_KINDS: readonly ["missing_capability", "wrong_or_unclear_description", "bug", "missing_data", "suggestion"];
164
+ export declare const SURFACE_FEEDBACK_MESSAGE_MIN = 10;
165
+ export declare const SURFACE_FEEDBACK_MESSAGE_MAX = 4000;
166
+ /** At most this many custom fields per `custom_fields_add` write. */
167
+ export declare const CUSTOM_FIELDS_MAX_PER_WRITE = 10;
168
+ /**
169
+ * THE PLATFORM WRITE TARGETS, declared once on the baseline surface. Each has
170
+ * the apply policy it always had: window fields and custom fields are `ask`
171
+ * (an agent's write waits for the person's card; a person's own write is the
172
+ * approval); feedback is `auto` (it changes nothing on the page). The runtime
173
+ * files word the live offer from these (`customFieldsTarget()` adds the
174
+ * mounted sections), and register the handlers that apply them.
175
+ */
176
+ export declare const PLATFORM_WRITE_TARGETS: {
177
+ readonly window_form_fields: {
178
+ readonly name: "window_form_fields";
179
+ readonly label: "Fields in an open window";
180
+ readonly description: 'Change fields in an open window that has no registered surface (listed in your context as window::<title>). Value: { "window": "<title exactly as listed>", "changes": [{ "field": "<field key>", "value": <new value> }] }. A text or number field takes a string or number, a checkbox or switch takes true/false, a list takes the text of one of its choices (a wrong choice is refused with the real ones). Every change is checked against the field\'s rules before anything lands; the person approves first, and the window\'s own Save still decides.';
181
+ readonly valueType: "object";
182
+ readonly mode: "draft";
183
+ readonly applyPolicy: "ask";
184
+ };
185
+ readonly surface_feedback: {
186
+ readonly name: "surface_feedback";
187
+ readonly label: "Feedback about this page";
188
+ readonly description: string;
189
+ readonly valueType: "object";
190
+ readonly mode: "entity";
191
+ readonly applyPolicy: "auto";
192
+ };
193
+ readonly custom_fields_add: {
194
+ readonly name: "custom_fields_add";
195
+ readonly label: "Add custom fields";
196
+ readonly description: string;
197
+ readonly valueType: "object";
198
+ readonly mode: "entity";
199
+ readonly applyPolicy: "ask";
200
+ };
201
+ readonly custom_fields_set: {
202
+ readonly name: "custom_fields_set";
203
+ readonly label: "Fill in custom fields";
204
+ readonly description: string;
205
+ readonly valueType: "object";
206
+ readonly mode: "entity";
207
+ readonly applyPolicy: "ask";
208
+ };
209
+ };
210
+ export type PlatformWriteTargetName = keyof typeof PLATFORM_WRITE_TARGETS;
211
+ export declare function isPlatformWriteTarget(name: string): name is PlatformWriteTargetName;
153
212
  /** Names only the platform writes — a manifest declaring one is refused. */
154
213
  export declare const PLATFORM_RESERVED_NAMES: {
155
214
  values: ReadonlyArray<string>;
@@ -101,9 +101,57 @@ const PLATFORM_CONTEXT_VALUES = {
101
101
  sortOrder: 9997
102
102
  }
103
103
  };
104
+ const BASELINE_SURFACE_NAME = "matrx-platform/baseline";
105
+ const SURFACE_FEEDBACK_KINDS = [
106
+ "missing_capability",
107
+ "wrong_or_unclear_description",
108
+ "bug",
109
+ "missing_data",
110
+ "suggestion"
111
+ ];
112
+ const SURFACE_FEEDBACK_MESSAGE_MIN = 10;
113
+ const SURFACE_FEEDBACK_MESSAGE_MAX = 4e3;
114
+ const CUSTOM_FIELDS_MAX_PER_WRITE = 10;
115
+ const PLATFORM_WRITE_TARGETS = {
116
+ window_form_fields: {
117
+ name: "window_form_fields",
118
+ label: "Fields in an open window",
119
+ description: `Change fields in an open window that has no registered surface (listed in your context as window::<title>). Value: { "window": "<title exactly as listed>", "changes": [{ "field": "<field key>", "value": <new value> }] }. A text or number field takes a string or number, a checkbox or switch takes true/false, a list takes the text of one of its choices (a wrong choice is refused with the real ones). Every change is checked against the field's rules before anything lands; the person approves first, and the window's own Save still decides.`,
120
+ valueType: "object",
121
+ mode: "draft",
122
+ applyPolicy: "ask"
123
+ },
124
+ surface_feedback: {
125
+ name: "surface_feedback",
126
+ label: "Feedback about this page",
127
+ description: `Save feedback about this page (surface) for the team that builds it \u2014 it changes NOTHING on the page and nobody is asked. Use it when something about the page got in your way: a write you needed does not exist, a description was wrong or unclear, data you needed was missing or arrived in an awkward form, or something broke. Also use it when the person asks you to give feedback on this page. Value: { "kind": "${SURFACE_FEEDBACK_KINDS.join(" | ")}", "message": "<what happened and what would have helped, ${SURFACE_FEEDBACK_MESSAGE_MIN}-${SURFACE_FEEDBACK_MESSAGE_MAX} characters>", "target_or_value": "<optional: the write target or value name it concerns>" }. It is filed for the surface named on this line; pass \`surface\` to file it for another open surface instead.`,
128
+ valueType: "object",
129
+ mode: "entity",
130
+ applyPolicy: "auto"
131
+ },
132
+ custom_fields_add: {
133
+ name: "custom_fields_add",
134
+ label: "Add custom fields",
135
+ description: `Add custom fields (new columns) to the kind of record shown on this page \u2014 SAVED immediately after the person approves, and every record of that kind gets the field (empty until filled in). Value: { "entity": "<entity token, required only when more than one section is listed>", "fields": [{ "label": "<the field's name as a person reads it>", "type": "<one of the types below; default text>" }] } \u2014 1 to ${CUSTOM_FIELDS_MAX_PER_WRITE} fields. Refused before the person is asked: a name already used, a name asked for twice, an unknown type, or a person who may not add fields (the store's reason is returned). This adds the FIELD only; it does not fill in a value.`,
136
+ valueType: "object",
137
+ mode: "entity",
138
+ applyPolicy: "ask"
139
+ },
140
+ custom_fields_set: {
141
+ name: "custom_fields_set",
142
+ label: "Fill in custom fields",
143
+ description: `Set this record's custom-field values \u2014 SAVED immediately after the person approves; a field left out is left alone. Value: { "entity": "<entity token, only when more than one section is listed>", "values": { "<field name or key>": <value> } }. A number field takes a number, a date field an ISO date (YYYY-MM-DD), text fields a string; null clears a value. Refused before the person is asked: a field that does not exist (the existing ones are named), or a person who may not fill them in. To add a NEW field use custom_fields_add.`,
144
+ valueType: "object",
145
+ mode: "entity",
146
+ applyPolicy: "ask"
147
+ }
148
+ };
149
+ function isPlatformWriteTarget(name) {
150
+ return Object.prototype.hasOwnProperty.call(PLATFORM_WRITE_TARGETS, name);
151
+ }
104
152
  const PLATFORM_RESERVED_NAMES = {
105
153
  values: Object.keys(PLATFORM_CONTEXT_VALUES),
106
- writeTargets: ["window_form_fields", "surface_feedback", "custom_fields_add", "custom_fields_set"]
154
+ writeTargets: Object.keys(PLATFORM_WRITE_TARGETS)
107
155
  };
108
156
  const PAGE_OFF_WITHHELD = [
109
157
  "route_brief",
@@ -140,16 +188,23 @@ function withAllBaselines(surfaceSpecific) {
140
188
  }
141
189
  export {
142
190
  AMBIENT_VALUES,
191
+ BASELINE_SURFACE_NAME,
143
192
  BASELINE_VALUES,
144
193
  BASELINE_VALUE_NAMES,
194
+ CUSTOM_FIELDS_MAX_PER_WRITE,
145
195
  OWN_CONVERSATION_WITHHELD,
146
196
  PAGELESS_CONTENT_INLINE_CEILING,
147
197
  PAGE_OFF_WITHHELD,
148
198
  PERSON_CONTEXT_VALUES,
149
199
  PLATFORM_CONTEXT_VALUES,
150
200
  PLATFORM_RESERVED_NAMES,
201
+ PLATFORM_WRITE_TARGETS,
151
202
  POINTER_INLINE_CEILINGS,
203
+ SURFACE_FEEDBACK_KINDS,
204
+ SURFACE_FEEDBACK_MESSAGE_MAX,
205
+ SURFACE_FEEDBACK_MESSAGE_MIN,
152
206
  allBaseline,
207
+ isPlatformWriteTarget,
153
208
  mergeBaselineValues,
154
209
  pickBaseline,
155
210
  withAllBaselines
@@ -1,7 +1,7 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { SurfaceScopePayload } from "../types.js";
3
3
  import type { InstanceContextEntry } from "../../agents/types/instance.types.js";
4
- import type { SurfaceHandle } from "@ai-matrx/kit/content-transfer";
4
+ import type { SurfaceHandle } from "@ai-matrx/alchemy/operate";
5
5
  import type { ApplySurfaceWriteOptions } from "./surface-writeback.js";
6
6
  /**
7
7
  * What a write handler may RETURN so the caller (and, for an agent write, the
@@ -8,9 +8,10 @@
8
8
  * that page could add a custom field; an agent on the same page could not —
9
9
  * nothing offered it. Declaring a target on every manifest would be 643 copies
10
10
  * of one sentence, so this is a PLATFORM target like `surface_feedback` and
11
- * `window_form_fields`: declared here once, belonging to no manifest, offered
12
- * whenever at least one custom-fields section is mounted, and routed by the
13
- * writeback seam (`surface-writeback.ts`) to the section's own door.
11
+ * `window_form_fields`: declared once on the baseline surface every surface
12
+ * inherits (`_baseline.manifest.ts`), offered whenever at least one
13
+ * custom-fields section is mounted, and written through the one write door
14
+ * (`surface-writeback.ts` `applyPlatformWrite`) to the section's own door.
14
15
  *
15
16
  * The section owns the write: it hands its host a door (records-ui
16
17
  * `agentDoor` prop → `CustomFieldsAgentDoor`), which checks the whole request
@@ -23,11 +24,10 @@
23
24
  */
24
25
  import type { SurfaceWriteTarget } from "../types.js";
25
26
  import type { SurfaceWriteOutcome } from "./SurfaceRuntimeContext.js";
26
- export declare const CUSTOM_FIELDS_TARGET_NAME = "custom_fields_add";
27
- export declare const CUSTOM_FIELDS_SET_TARGET_NAME = "custom_fields_set";
27
+ export declare const CUSTOM_FIELDS_TARGET_NAME: "custom_fields_add";
28
+ export declare const CUSTOM_FIELDS_SET_TARGET_NAME: "custom_fields_set";
28
29
  /** The surface value a section contributes when its surface declares it (baseline `custom_fields`). */
29
30
  export declare const CUSTOM_FIELDS_VALUE_NAME = "custom_fields";
30
- export declare const CUSTOM_FIELDS_MAX_PER_WRITE = 10;
31
31
  export declare function providerOwnsCustomFields(surfaceName: string): boolean;
32
32
  export interface CustomFieldAddRequest {
33
33
  label: string;
@@ -1,7 +1,7 @@
1
- const CUSTOM_FIELDS_TARGET_NAME = "custom_fields_add";
2
- const CUSTOM_FIELDS_SET_TARGET_NAME = "custom_fields_set";
1
+ import { CUSTOM_FIELDS_MAX_PER_WRITE, PLATFORM_WRITE_TARGETS } from "../manifests/_baseline.manifest.js";
2
+ const CUSTOM_FIELDS_TARGET_NAME = PLATFORM_WRITE_TARGETS.custom_fields_add.name;
3
+ const CUSTOM_FIELDS_SET_TARGET_NAME = PLATFORM_WRITE_TARGETS.custom_fields_set.name;
3
4
  const CUSTOM_FIELDS_VALUE_NAME = "custom_fields";
4
- const CUSTOM_FIELDS_MAX_PER_WRITE = 10;
5
5
  const PROVIDER_OWNED_CUSTOM_FIELDS = /* @__PURE__ */ new Set(["matrx-user/notes"]);
6
6
  function providerOwnsCustomFields(surfaceName) {
7
7
  return PROVIDER_OWNED_CUSTOM_FIELDS.has(surfaceName);
@@ -31,20 +31,13 @@ function describeDoor(door) {
31
31
  const may = s.mayAdd === true ? "you may add" : s.mayAdd === null ? "still checking who may add" : `adding is refused: ${s.refusal ?? "this person may not add a field here"}`;
32
32
  return `entity "${s.entityToken}" (${what}, record ${s.recordId}) \u2014 fields: ${held}; ${may}`;
33
33
  }
34
- const BASE_DESCRIPTION = `Add custom fields (new columns) to the kind of record shown on this page \u2014 SAVED immediately after the person approves, and every record of that kind gets the field (empty until filled in). Value: { "entity": "<entity token, required only when more than one section is listed>", "fields": [{ "label": "<the field's name as a person reads it>", "type": "<one of the types below; default text>" }] } \u2014 1 to ${CUSTOM_FIELDS_MAX_PER_WRITE} fields. Refused before the person is asked: a name already used, a name asked for twice, an unknown type, or a person who may not add fields (the store's reason is returned). This adds the FIELD only; it does not fill in a value.`;
35
34
  function customFieldsTarget() {
36
35
  const mounted = listCustomFieldsDoors();
37
36
  const types = mounted[0]?.state().types ?? [];
38
37
  const typeLine = types.length > 0 ? ` Types: ${types.map((t) => `${t.value} (${t.label})`).join(", ")}.` : "";
39
38
  const sections = mounted.length > 0 ? ` Sections on this page: ${mounted.map(describeDoor).join(" | ")}.` : "";
40
- return {
41
- name: CUSTOM_FIELDS_TARGET_NAME,
42
- label: "Add custom fields",
43
- description: `${BASE_DESCRIPTION}${typeLine}${sections}`,
44
- valueType: "object",
45
- mode: "entity",
46
- applyPolicy: "ask"
47
- };
39
+ const declared = PLATFORM_WRITE_TARGETS.custom_fields_add;
40
+ return { ...declared, description: `${declared.description}${typeLine}${sections}` };
48
41
  }
49
42
  function customFieldsProblems(value, mounted = listCustomFieldsDoors()) {
50
43
  const none = { door: null, fields: [] };
@@ -151,14 +144,8 @@ function hasCustomFieldsSetDoors() {
151
144
  function customFieldsSetTarget() {
152
145
  const mounted = listCustomFieldsDoors().filter((d) => typeof d.setValues === "function");
153
146
  const sections = mounted.length > 0 ? ` Sections on this page: ${mounted.map(describeDoor).join(" | ")}.` : "";
154
- return {
155
- name: CUSTOM_FIELDS_SET_TARGET_NAME,
156
- label: "Fill in custom fields",
157
- description: `Set this record's custom-field values \u2014 SAVED immediately after the person approves; a field left out is left alone. Value: { "entity": "<entity token, only when more than one section is listed>", "values": { "<field name or key>": <value> } }. A number field takes a number, a date field an ISO date (YYYY-MM-DD), text fields a string; null clears a value. Refused before the person is asked: a field that does not exist (the existing ones are named), or a person who may not fill them in. To add a NEW field use ${CUSTOM_FIELDS_TARGET_NAME}.${sections}`,
158
- valueType: "object",
159
- mode: "entity",
160
- applyPolicy: "ask"
161
- };
147
+ const declared = PLATFORM_WRITE_TARGETS.custom_fields_set;
148
+ return { ...declared, description: `${declared.description}${sections}` };
162
149
  }
163
150
  function customFieldsSetProblems(value, mounted = listCustomFieldsDoors().filter((d) => typeof d.setValues === "function")) {
164
151
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -197,7 +184,6 @@ async function applyCustomFieldsSetWrite(value) {
197
184
  };
198
185
  }
199
186
  export {
200
- CUSTOM_FIELDS_MAX_PER_WRITE,
201
187
  CUSTOM_FIELDS_SET_TARGET_NAME,
202
188
  CUSTOM_FIELDS_TARGET_NAME,
203
189
  CUSTOM_FIELDS_VALUE_NAME,
@@ -7,10 +7,10 @@
7
7
  * exam dates", "create_classes never said X"). The team reads it the next time
8
8
  * it updates that surface (`pnpm surface:feedback --surface <name>`).
9
9
  *
10
- * Like `window_form_fields` (`window-forms.ts`) it is declared here once and
11
- * belongs to no manifest: `listLiveWriteTargets` offers it whenever at least
12
- * one registered surface is mounted, and `applySurfaceWrite` routes it to
13
- * `applySurfaceFeedbackWrite` in the seam. It changes NOTHING on the page, so
10
+ * Like `window_form_fields` (`window-forms.ts`) it is declared once on the
11
+ * baseline surface (`_baseline.manifest.ts`): `listLiveWriteTargets` offers it
12
+ * whenever at least one registered surface is mounted, and it is written
13
+ * through the one write door (`surface-writeback.ts` `applyPlatformWrite`). It changes NOTHING on the page, so
14
14
  * it is `auto` — the person is never interrupted by a card for it.
15
15
  *
16
16
  * Storage is the central triage table `users.user_feedback`, written through
@@ -20,18 +20,16 @@
20
20
  */
21
21
  import type { ChatFeedbackInput, ChatFeedbackType } from "../../host/contract.js";
22
22
  import type { SurfaceWriteTarget } from "../types.js";
23
+ import { SURFACE_FEEDBACK_KINDS } from "../manifests/_baseline.manifest.js";
23
24
  import type { SurfaceWriteOutcome } from "./SurfaceRuntimeContext.js";
24
- export declare const SURFACE_FEEDBACK_TARGET_NAME = "surface_feedback";
25
- export declare const SURFACE_FEEDBACK_KINDS: readonly ["missing_capability", "wrong_or_unclear_description", "bug", "missing_data", "suggestion"];
25
+ export declare const SURFACE_FEEDBACK_TARGET_NAME: "surface_feedback";
26
26
  export type SurfaceFeedbackKind = (typeof SURFACE_FEEDBACK_KINDS)[number];
27
- export declare const SURFACE_FEEDBACK_MESSAGE_MIN = 10;
28
- export declare const SURFACE_FEEDBACK_MESSAGE_MAX = 4000;
29
27
  export interface SurfaceFeedbackValue {
30
28
  kind: SurfaceFeedbackKind;
31
29
  message: string;
32
30
  target_or_value?: string;
33
31
  }
34
- /** The platform target — declared once; offered whenever a surface is mounted. */
32
+ /** The platform target — declared once on the baseline surface (`_baseline.manifest.ts`); offered whenever a surface is mounted. */
35
33
  export declare const SURFACE_FEEDBACK_TARGET: SurfaceWriteTarget;
36
34
  /**
37
35
  * Pre-write check. Throws a sentence the agent can act on; nothing is written
@@ -1,24 +1,14 @@
1
1
  import { getChatHost } from "../../host/configure.js";
2
2
  import { getStoreSingleton } from "../../store/store-singleton.js";
3
- const SURFACE_FEEDBACK_TARGET_NAME = "surface_feedback";
4
- const SURFACE_FEEDBACK_KINDS = [
5
- "missing_capability",
6
- "wrong_or_unclear_description",
7
- "bug",
8
- "missing_data",
9
- "suggestion"
10
- ];
11
- const SURFACE_FEEDBACK_MESSAGE_MIN = 10;
12
- const SURFACE_FEEDBACK_MESSAGE_MAX = 4e3;
3
+ import {
4
+ PLATFORM_WRITE_TARGETS,
5
+ SURFACE_FEEDBACK_KINDS,
6
+ SURFACE_FEEDBACK_MESSAGE_MAX,
7
+ SURFACE_FEEDBACK_MESSAGE_MIN
8
+ } from "../manifests/_baseline.manifest.js";
9
+ const SURFACE_FEEDBACK_TARGET_NAME = PLATFORM_WRITE_TARGETS.surface_feedback.name;
13
10
  const KIND_LIST = SURFACE_FEEDBACK_KINDS.join(" | ");
14
- const SURFACE_FEEDBACK_TARGET = {
15
- name: SURFACE_FEEDBACK_TARGET_NAME,
16
- label: "Feedback about this page",
17
- description: `Save feedback about this page (surface) for the team that builds it \u2014 it changes NOTHING on the page and nobody is asked. Use it when something about the page got in your way: a write you needed does not exist, a description was wrong or unclear, data you needed was missing or arrived in an awkward form, or something broke. Also use it when the person asks you to give feedback on this page. Value: { "kind": "${KIND_LIST}", "message": "<what happened and what would have helped, ${SURFACE_FEEDBACK_MESSAGE_MIN}-${SURFACE_FEEDBACK_MESSAGE_MAX} characters>", "target_or_value": "<optional: the write target or value name it concerns>" }. It is filed for the surface named on this line; pass \`surface\` to file it for another open surface instead.`,
18
- valueType: "object",
19
- mode: "entity",
20
- applyPolicy: "auto"
21
- };
11
+ const SURFACE_FEEDBACK_TARGET = PLATFORM_WRITE_TARGETS.surface_feedback;
22
12
  const ALLOWED_KEYS = /* @__PURE__ */ new Set(["kind", "message", "target_or_value"]);
23
13
  function validateSurfaceFeedback(value) {
24
14
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -101,9 +91,6 @@ async function saveSurfaceFeedback(value, ctx) {
101
91
  };
102
92
  }
103
93
  export {
104
- SURFACE_FEEDBACK_KINDS,
105
- SURFACE_FEEDBACK_MESSAGE_MAX,
106
- SURFACE_FEEDBACK_MESSAGE_MIN,
107
94
  SURFACE_FEEDBACK_TARGET,
108
95
  SURFACE_FEEDBACK_TARGET_NAME,
109
96
  buildSurfaceFeedbackInput,
@@ -233,8 +233,11 @@ export declare function registerSurfaceWritePolicies(policies: WritePolicyMap, k
233
233
  export declare const surfaceWriteApprovals: ApprovalPort;
234
234
  /**
235
235
  * The declarations the door reads: every manifest, with each target's
236
- * EFFECTIVE policy. No `ancestry`: a resolved manifest already carries what it
237
- * inherits, so a target resolves on the surface that is writing.
236
+ * EFFECTIVE policy, plus the BASELINE surface (`BASELINE_SURFACE_NAME`), which
237
+ * declares the platform's own targets (`PLATFORM_WRITE_TARGETS`). The only
238
+ * ancestry is the baseline: a resolved manifest already carries what it
239
+ * inherits from its parents, so a surface's own target resolves on the surface
240
+ * that is writing, and a platform target on the baseline every surface sits on.
238
241
  */
239
242
  export declare const surfaceWriteDeclarations: WriteDoorDeclarations;
240
243
  /**
@@ -1,6 +1,11 @@
1
1
  "use client";
2
2
  import { contentIrKindValidator } from "../../host/content-ir-slots.js";
3
3
  import { getManifest } from "./registry.js";
4
+ import {
5
+ BASELINE_SURFACE_NAME,
6
+ isPlatformWriteTarget,
7
+ PLATFORM_WRITE_TARGETS
8
+ } from "../manifests/_baseline.manifest.js";
4
9
  import {
5
10
  isSurfaceWritePatch,
6
11
  resolveSurfaceWritePatch
@@ -350,6 +355,9 @@ const surfaceWriteKinds = {
350
355
  };
351
356
  const surfaceWriteDeclarations = {
352
357
  get(surfaceName) {
358
+ if (surfaceName === BASELINE_SURFACE_NAME) {
359
+ return { writeTargets: Object.values(PLATFORM_WRITE_TARGETS) };
360
+ }
353
361
  const manifest = getManifest(surfaceName);
354
362
  if (!manifest) return void 0;
355
363
  return {
@@ -359,6 +367,9 @@ const surfaceWriteDeclarations = {
359
367
  })),
360
368
  ...manifest.itemTypes ? { itemTypes: manifest.itemTypes } : {}
361
369
  };
370
+ },
371
+ ancestry(surfaceName) {
372
+ return surfaceName === BASELINE_SURFACE_NAME ? [] : [BASELINE_SURFACE_NAME];
362
373
  }
363
374
  };
364
375
  let surfaceDoor = null;
@@ -375,6 +386,7 @@ function loadSurfaceWriteDoor() {
375
386
  })
376
387
  ).then((door) => {
377
388
  routeMountedPages(door);
389
+ registerPlatformHandlers(door);
378
390
  return door;
379
391
  });
380
392
  surfaceDoor = loading;
@@ -444,6 +456,7 @@ function routeMountedPages(door) {
444
456
  const routePageWrite = (request) => {
445
457
  const seam = request.runId ? seamWrites.get(request.runId) : void 0;
446
458
  if (seam) return seam.apply();
459
+ if (isPlatformWriteTarget(request.target.name)) return applyPlatformHeadless(request);
447
460
  return applyMountedPageWrite(request);
448
461
  };
449
462
  async function applyMountedPageWrite(request) {
@@ -462,7 +475,12 @@ async function applyMountedPageWrite(request) {
462
475
  }
463
476
  if (handler.validate) await handler.validate(request.value);
464
477
  const outcome = toWriteOutcome(await handler.apply(request.value));
465
- return { status: "applied", ...outcome?.summary ? { sentence: outcome.summary } : {} };
478
+ return {
479
+ status: "applied",
480
+ ...outcome?.summary ? { sentence: outcome.summary } : {},
481
+ // What the page's handler produced (ids of created records) rides the receipt (alchemy 0.12.3).
482
+ ...outcome?.data !== void 0 ? { result: outcome.data } : {}
483
+ };
466
484
  }
467
485
  async function writeThroughDoor(request, conversationId, apply, approve) {
468
486
  const door = await loadSurfaceWriteDoor();
@@ -743,17 +761,8 @@ async function applySurfaceWrite(targetName, rawValue, opts) {
743
761
  async function applySurfaceWriteNow(targetName, rawValue, opts) {
744
762
  const registry = opts?.source ?? getGlobalSurfaceRegistry();
745
763
  const onScreen = registry.kind === "global";
746
- if (onScreen && targetName === WINDOW_FORM_TARGET_NAME) {
747
- return applyWindowFormWrite(rawValue, opts);
748
- }
749
- if (onScreen && targetName === SURFACE_FEEDBACK_TARGET_NAME) {
750
- return applySurfaceFeedbackWrite(rawValue, opts);
751
- }
752
- if (onScreen && targetName === CUSTOM_FIELDS_TARGET_NAME) {
753
- return applyCustomFieldsTargetWrite(rawValue, opts, "add");
754
- }
755
- if (onScreen && targetName === CUSTOM_FIELDS_SET_TARGET_NAME) {
756
- return applyCustomFieldsTargetWrite(rawValue, opts, "set");
764
+ if (onScreen && isPlatformWriteTarget(targetName)) {
765
+ return applyPlatformWrite(targetName, rawValue, opts);
757
766
  }
758
767
  const stack = registry.stack().filter(
759
768
  (entry) => !opts?.surfaceName || entry.surfaceName === opts.surfaceName
@@ -984,157 +993,184 @@ async function applySurfaceWriteNow(targetName, rawValue, opts) {
984
993
  }
985
994
  );
986
995
  }
987
- async function applyWindowFormWrite(rawValue, opts) {
988
- const target = WINDOW_FORM_TARGET;
989
- const primary = getSurfaceRuntimeStack()[0];
990
- rawValue = labelWindowFormWrite(rawValue);
991
- const surfaceName = primary?.surfaceName ?? "";
992
- if ((opts?.origin ?? "user") === "agent") {
993
- const verdict = await agentWriteAllowed(
994
- target,
995
- surfaceName,
996
- opts?.actorLabel,
997
- rawValue,
998
- opts?.requestApproval,
999
- primary ?? { surfaceName, getScope: () => ({}) }
1000
- );
1001
- if (verdict !== true) return verdict;
1002
- }
1003
- try {
1004
- await applyWindowFormChanges(rawValue);
1005
- if (!opts?.quiet) toast.success(`${target.label} \u2014 filled in. Review and save.`);
1006
- return { ok: true, surfaceName, target, change: writeReceipt(target, rawValue, NOT_READ) };
1007
- } catch (error) {
1008
- const message = error instanceof Error ? error.message : `Applying "${target.label}" failed.`;
1009
- if (error instanceof SurfaceWriteRefusalError) {
1010
- return { ok: false, refused: true, error: message };
996
+ async function applyPlatformTarget(name, value, context) {
997
+ switch (name) {
998
+ case "window_form_fields":
999
+ await applyWindowFormChanges(value);
1000
+ return void 0;
1001
+ case "surface_feedback": {
1002
+ validateSurfaceFeedback(value);
1003
+ return saveSurfaceFeedback(value, {
1004
+ surfaceName: context.surfaceName,
1005
+ route: typeof window === "undefined" ? "" : window.location.pathname,
1006
+ organizationId: await feedbackOrganizationId(),
1007
+ ...context.conversationId ? { conversationId: context.conversationId } : {},
1008
+ ...context.agentId ? { agentId: context.agentId } : {},
1009
+ ...context.actorLabel ? { agentName: context.actorLabel } : {}
1010
+ });
1011
1011
  }
1012
- return fail(message, { targetName: target.name, surfaceName, error });
1012
+ case "custom_fields_add":
1013
+ if (!hasCustomFieldsDoors()) refuseSurfaceWrite("No custom-fields section is open on this page.");
1014
+ validateCustomFieldsWrite(value);
1015
+ return asRefusal(() => applyCustomFieldsWrite(value));
1016
+ case "custom_fields_set":
1017
+ if (!hasCustomFieldsSetDoors()) refuseSurfaceWrite("No custom-fields section is open on this page.");
1018
+ validateCustomFieldsSetWrite(value);
1019
+ return asRefusal(() => applyCustomFieldsSetWrite(value));
1013
1020
  }
1014
1021
  }
1015
- async function applySurfaceFeedbackWrite(rawValue, opts) {
1016
- const target = SURFACE_FEEDBACK_TARGET;
1017
- const stack = getSurfaceRuntimeStack();
1018
- const mounted = stack.map((entry) => entry.surfaceName);
1019
- if (stack.length === 0) {
1020
- return failUnapplicable(
1021
- unapplicableMessage(target.name, "This screen mounts no registered surface to give feedback on."),
1022
- { targetName: target.name }
1023
- );
1024
- }
1025
- const runtime = opts?.surfaceName ? stack.find((entry) => entry.surfaceName === opts.surfaceName) : stack[0];
1026
- if (!runtime) {
1027
- return refuseBeforeApproval(
1028
- `Surface "${opts?.surfaceName}" is not open here, so feedback cannot be filed for it. Open surfaces: ${mounted.join(", ")}. Nothing was saved.`,
1029
- { targetName: target.name, surfaceName: opts?.surfaceName, mounted }
1030
- );
1031
- }
1032
- const surfaceName = runtime.surfaceName;
1033
- const typed = coerceDeclaredValueType(target, rawValue);
1034
- if (!typed.ok) {
1035
- return refuseBeforeApproval(typed.error, { targetName: target.name, surfaceName });
1036
- }
1037
- const value = typed.value;
1022
+ async function asRefusal(run) {
1038
1023
  try {
1039
- validateSurfaceFeedback(value);
1024
+ return await run();
1040
1025
  } catch (error) {
1041
- return refuseBeforeApproval(
1042
- error instanceof Error ? error.message : `"${target.label}" refused this value.`,
1043
- { targetName: target.name, surfaceName }
1044
- );
1045
- }
1046
- if ((opts?.origin ?? "user") === "agent") {
1047
- const verdict = await agentWriteAllowed(
1048
- target,
1049
- surfaceName,
1050
- opts?.actorLabel,
1051
- value,
1052
- opts?.requestApproval,
1053
- runtime
1054
- );
1055
- if (verdict !== true) return verdict;
1056
- }
1057
- let organizationId = readActiveOrganizationId();
1058
- if (!organizationId) {
1059
- const resolved = await awaitEffectiveOrganizationId();
1060
- if (resolved.status === "ready") organizationId = resolved.organizationId;
1061
- else
1062
- return {
1063
- ok: false,
1064
- refused: true,
1065
- phase: "apply",
1066
- error: resolved.cause === "unreadable" ? "The person's organization could not be read just now, so nothing was saved. Tell them their feedback was not filed and to send it again in a moment." : "No organization is selected, and every feedback row is filed under one, so nothing was saved. Tell the person their feedback could not be filed until they pick their organization from the avatar menu, then send it again."
1067
- };
1026
+ if (error instanceof SurfaceWriteRefusalError) throw error;
1027
+ throw new SurfaceWriteRefusalError(error instanceof Error && error.message ? error.message : "The write did not complete.");
1068
1028
  }
1029
+ }
1030
+ async function feedbackOrganizationId() {
1031
+ const active = readActiveOrganizationId();
1032
+ if (active) return active;
1033
+ const resolved = await awaitEffectiveOrganizationId();
1034
+ if (resolved.status === "ready") return resolved.organizationId;
1035
+ refuseSurfaceWrite(
1036
+ resolved.cause === "unreadable" ? "The person's organization could not be read just now, so nothing was saved. Tell them their feedback was not filed and to send it again in a moment." : "No organization is selected, and every feedback row is filed under one, so nothing was saved. Tell the person their feedback could not be filed until they pick their organization from the avatar menu, then send it again."
1037
+ );
1038
+ }
1039
+ async function applyPlatformHeadless(request) {
1040
+ const name = request.target.name;
1041
+ if (!isPlatformWriteTarget(name)) throw new Error(`"${name}" is not a platform write target.`);
1042
+ if (request.ops) throw new Error(`"${request.target.label}" takes a whole value; send it instead of edits.`);
1069
1043
  try {
1070
- const outcome = await saveSurfaceFeedback(value, {
1071
- surfaceName,
1072
- route: typeof window === "undefined" ? "" : window.location.pathname,
1073
- organizationId,
1074
- ...opts?.conversationId ? { conversationId: opts.conversationId } : {},
1075
- ...opts?.agentId ? { agentId: opts.agentId } : {},
1076
- ...opts?.actorLabel ? { agentName: opts.actorLabel } : {}
1044
+ const outcome = await applyPlatformTarget(name, request.value, {
1045
+ surfaceName: request.surfaceName === BASELINE_SURFACE_NAME ? "" : request.surfaceName
1077
1046
  });
1078
- if (!opts?.quiet) {
1079
- toast.success("Feedback saved for the team.", { description: surfaceName });
1080
- }
1081
- return { ok: true, surfaceName, target, outcome, change: writeReceipt(target, value, NOT_READ) };
1047
+ return {
1048
+ status: "applied",
1049
+ ...outcome?.summary ? { sentence: outcome.summary } : {},
1050
+ ...outcome?.data !== void 0 ? { result: outcome.data } : {}
1051
+ };
1082
1052
  } catch (error) {
1083
- const failure = fail(
1084
- error instanceof Error && error.message ? error.message : `Saving "${target.label}" failed.`,
1085
- { targetName: target.name, surfaceName, error }
1086
- );
1087
- return failure.ok ? failure : { ...failure, phase: "apply" };
1053
+ if (error instanceof SurfaceWriteRefusalError) throw reportedError(error.message);
1054
+ throw error;
1055
+ }
1056
+ }
1057
+ let platformHandlersOn = null;
1058
+ function registerPlatformHandlers(door) {
1059
+ if (platformHandlersOn === door) return;
1060
+ platformHandlersOn = door;
1061
+ for (const name of Object.keys(PLATFORM_WRITE_TARGETS)) {
1062
+ door.registerHeadless(BASELINE_SURFACE_NAME, name, (request) => applyPlatformHeadless(request));
1088
1063
  }
1089
1064
  }
1090
- async function applyCustomFieldsTargetWrite(rawValue, opts, mode) {
1091
- const name = mode === "add" ? CUSTOM_FIELDS_TARGET_NAME : CUSTOM_FIELDS_SET_TARGET_NAME;
1092
- if (mode === "add" ? !hasCustomFieldsDoors() : !hasCustomFieldsSetDoors()) {
1065
+ function platformTarget(name) {
1066
+ if (name === CUSTOM_FIELDS_TARGET_NAME) return customFieldsTarget();
1067
+ if (name === CUSTOM_FIELDS_SET_TARGET_NAME) return customFieldsSetTarget();
1068
+ return PLATFORM_WRITE_TARGETS[name];
1069
+ }
1070
+ async function applyPlatformWrite(name, rawValue, opts) {
1071
+ const stack = getSurfaceRuntimeStack();
1072
+ let runtime = stack[0];
1073
+ if (name === SURFACE_FEEDBACK_TARGET_NAME) {
1074
+ const mounted = stack.map((entry) => entry.surfaceName);
1075
+ if (stack.length === 0) {
1076
+ return failUnapplicable(
1077
+ unapplicableMessage(name, "This screen mounts no registered surface to give feedback on."),
1078
+ { targetName: name }
1079
+ );
1080
+ }
1081
+ runtime = opts?.surfaceName ? stack.find((entry) => entry.surfaceName === opts.surfaceName) : stack[0];
1082
+ if (!runtime) {
1083
+ return refuseBeforeApproval(
1084
+ `Surface "${opts?.surfaceName}" is not open here, so feedback cannot be filed for it. Open surfaces: ${mounted.join(", ")}. Nothing was saved.`,
1085
+ { targetName: name, surfaceName: opts?.surfaceName, mounted }
1086
+ );
1087
+ }
1088
+ }
1089
+ if (name === CUSTOM_FIELDS_TARGET_NAME ? !hasCustomFieldsDoors() : name === CUSTOM_FIELDS_SET_TARGET_NAME && !hasCustomFieldsSetDoors()) {
1093
1090
  return failUnapplicable(
1094
1091
  unapplicableMessage(name, "No custom-fields section is open on this page."),
1095
1092
  { targetName: name }
1096
1093
  );
1097
1094
  }
1098
- const target = mode === "add" ? customFieldsTarget() : customFieldsSetTarget();
1099
- const primary = getSurfaceRuntimeStack()[0];
1100
- const surfaceName = primary?.surfaceName ?? "";
1095
+ const target = platformTarget(name);
1096
+ const surfaceName = runtime?.surfaceName ?? "";
1097
+ const approvalRuntime = runtime ?? { surfaceName, getScope: () => ({}) };
1101
1098
  const typed = coerceDeclaredValueType(target, rawValue);
1102
1099
  if (!typed.ok) {
1103
- return refuseBeforeApproval(typed.error, { targetName: target.name, surfaceName });
1100
+ return refuseBeforeApproval(typed.error, { targetName: name, surfaceName });
1104
1101
  }
1105
- const value = typed.value;
1102
+ const value = name === WINDOW_FORM_TARGET_NAME ? labelWindowFormWrite(typed.value) : typed.value;
1106
1103
  try {
1107
- if (mode === "add") validateCustomFieldsWrite(value);
1108
- else validateCustomFieldsSetWrite(value);
1104
+ if (name === SURFACE_FEEDBACK_TARGET_NAME) validateSurfaceFeedback(value);
1105
+ if (name === CUSTOM_FIELDS_TARGET_NAME) validateCustomFieldsWrite(value);
1106
+ if (name === CUSTOM_FIELDS_SET_TARGET_NAME) validateCustomFieldsSetWrite(value);
1109
1107
  } catch (error) {
1110
1108
  return refuseBeforeApproval(
1111
1109
  error instanceof Error ? error.message : `"${target.label}" refused this value.`,
1112
- { targetName: target.name, surfaceName }
1110
+ { targetName: name, surfaceName }
1113
1111
  );
1114
1112
  }
1115
- if ((opts?.origin ?? "user") === "agent") {
1116
- const verdict = await agentWriteAllowed(
1117
- target,
1118
- surfaceName,
1119
- opts?.actorLabel,
1120
- value,
1121
- opts?.requestApproval,
1122
- primary ?? { surfaceName, getScope: () => ({}) }
1123
- );
1113
+ const origin = opts?.origin ?? "user";
1114
+ const askAgent = () => agentWriteAllowed(target, surfaceName, opts?.actorLabel, value, opts?.requestApproval, approvalRuntime);
1115
+ if (origin === "agent" && target.applyPolicy === "auto") {
1116
+ const verdict = await askAgent();
1124
1117
  if (verdict !== true) return verdict;
1125
1118
  }
1126
- try {
1127
- const outcome = mode === "add" ? await applyCustomFieldsWrite(value) : await applyCustomFieldsSetWrite(value);
1128
- if (!opts?.quiet && outcome.summary) toast.success(outcome.summary);
1129
- return { ok: true, surfaceName, target, outcome, change: writeReceipt(target, value, NOT_READ) };
1130
- } catch (error) {
1131
- return {
1132
- ok: false,
1133
- refused: true,
1134
- phase: "apply",
1135
- error: error instanceof Error ? error.message : `"${target.label}" did not complete.`
1136
- };
1119
+ let settled;
1120
+ const approve = origin === "agent" ? async () => {
1121
+ const verdict = await askAgent();
1122
+ if (verdict === true) return true;
1123
+ settled = verdict;
1124
+ if (!verdict.ok && verdict.declined) return false;
1125
+ throw reportedError(verdict.ok ? `"${target.label}" was not applied.` : verdict.error);
1126
+ } : void 0;
1127
+ const apply = async () => {
1128
+ try {
1129
+ const outcome = await applyPlatformTarget(name, value, {
1130
+ surfaceName,
1131
+ ...opts?.conversationId ? { conversationId: opts.conversationId } : {},
1132
+ ...opts?.agentId ? { agentId: opts.agentId } : {},
1133
+ ...opts?.actorLabel ? { actorLabel: opts.actorLabel } : {}
1134
+ });
1135
+ if (!opts?.quiet) {
1136
+ if (name === WINDOW_FORM_TARGET_NAME) toast.success(`${target.label} \u2014 filled in. Review and save.`);
1137
+ else if (name === SURFACE_FEEDBACK_TARGET_NAME) toast.success("Feedback saved for the team.", { description: surfaceName });
1138
+ else if (outcome?.summary) toast.success(outcome.summary);
1139
+ }
1140
+ settled = {
1141
+ ok: true,
1142
+ surfaceName,
1143
+ target,
1144
+ ...outcome ? { outcome } : {},
1145
+ change: writeReceipt(target, value, NOT_READ)
1146
+ };
1147
+ return { status: "applied", ...outcome?.summary ? { sentence: outcome.summary } : {} };
1148
+ } catch (error) {
1149
+ const message = error instanceof Error && error.message ? error.message : `Applying "${target.label}" failed.`;
1150
+ if (error instanceof SurfaceWriteRefusalError) {
1151
+ settled = name === WINDOW_FORM_TARGET_NAME ? { ok: false, refused: true, error: message } : { ok: false, refused: true, phase: "apply", error: message };
1152
+ } else {
1153
+ const failure = fail(message, { targetName: name, surfaceName, error });
1154
+ settled = failure.ok ? failure : { ...failure, phase: "apply" };
1155
+ }
1156
+ throw reportedError(message);
1157
+ }
1158
+ };
1159
+ const receipt = await writeThroughDoor(
1160
+ { surfaceName: surfaceName || BASELINE_SURFACE_NAME, target: name, value, by: origin === "agent" ? "agent" : "person" },
1161
+ opts?.conversationId ?? null,
1162
+ apply,
1163
+ approve
1164
+ );
1165
+ if (receipt.status === "applied" && receipt.warning && !opts?.quiet) {
1166
+ toast.warning(receipt.warning, receipt.fix ? { description: receipt.fix } : void 0);
1137
1167
  }
1168
+ const result = settled ?? fail(receipt.status === "refused" ? receipt.sentence : `"${target.label}" was not applied: ${receipt.sentence}`, {
1169
+ targetName: name,
1170
+ surfaceName,
1171
+ receipt
1172
+ });
1173
+ return { ...result, receipt };
1138
1174
  }
1139
1175
  const SURFACE_WRITE_TOOL_NAME = "apply_surface_write";
1140
1176
  function listAgentWritableTargets(source) {
@@ -30,7 +30,7 @@
30
30
  * never read or written.
31
31
  */
32
32
  import type { SurfaceWriteTarget } from "../types.js";
33
- export declare const WINDOW_FORM_TARGET_NAME = "window_form_fields";
33
+ export declare const WINDOW_FORM_TARGET_NAME: "window_form_fields";
34
34
  /** The marker a REGISTERED layer puts on its root element. */
35
35
  export declare const SURFACE_LAYER_ATTRIBUTE = "data-surface-layer";
36
36
  export type WindowFormKind = "dialog" | "window" | "sheet" | "drawer";
@@ -48,7 +48,7 @@ export interface WindowForm {
48
48
  kind: WindowFormKind;
49
49
  fields: WindowFormField[];
50
50
  }
51
- /** The platform target — declared here once, offered only while a window form is open. */
51
+ /** The platform target — declared once on the baseline surface (`_baseline.manifest.ts`), offered only while a window form is open. */
52
52
  export declare const WINDOW_FORM_TARGET: SurfaceWriteTarget;
53
53
  /** Every open unregistered window that holds at least one field. */
54
54
  export declare function readWindowForms(): WindowForm[];
@@ -1,14 +1,8 @@
1
1
  import { refuseSurfaceWrite } from "./surface-writeback.js";
2
- const WINDOW_FORM_TARGET_NAME = "window_form_fields";
2
+ import { PLATFORM_WRITE_TARGETS } from "../manifests/_baseline.manifest.js";
3
+ const WINDOW_FORM_TARGET_NAME = PLATFORM_WRITE_TARGETS.window_form_fields.name;
3
4
  const SURFACE_LAYER_ATTRIBUTE = "data-surface-layer";
4
- const WINDOW_FORM_TARGET = {
5
- name: WINDOW_FORM_TARGET_NAME,
6
- label: "Fields in an open window",
7
- description: `Change fields in an open window that has no registered surface (listed in your context as window::<title>). Value: { "window": "<title exactly as listed>", "changes": [{ "field": "<field key>", "value": <new value> }] }. A text or number field takes a string or number, a checkbox or switch takes true/false, a list takes the text of one of its choices (a wrong choice is refused with the real ones). Every change is checked against the field's rules before anything lands; the person approves first, and the window's own Save still decides.`,
8
- valueType: "object",
9
- mode: "draft",
10
- applyPolicy: "ask"
11
- };
5
+ const WINDOW_FORM_TARGET = PLATFORM_WRITE_TARGETS.window_form_fields;
12
6
  const WINDOW_SELECTOR = '[role="dialog"], [role="alertdialog"], [data-window-panel]';
13
7
  const FIELD_SELECTOR = 'input, textarea, select, [contenteditable="true"], [role="switch"], [role="checkbox"], [role="combobox"]';
14
8
  const SKIPPED_INPUT_TYPES = /* @__PURE__ */ new Set([
@@ -29,7 +29,7 @@ export declare function YouTubeMark({ colored, className }: ConnectorLogoProps):
29
29
  export declare function GoogleDocsMark({ colored, className }: ConnectorLogoProps): import("react").JSX.Element;
30
30
  /** Google Sheets — the green page with a grid. */
31
31
  export declare function GoogleSheetsMark({ colored, className }: ConnectorLogoProps): import("react").JSX.Element;
32
- /** GitHub — the octocat. Brand color is black, so it always rides `currentColor`. */
33
- export declare function GitHubMark({ className }: ConnectorLogoProps): import("react").JSX.Element;
32
+ /** GitHub — the ONE GitHub glyph (`@ai-matrx/icons/brand`), as a connector mark. */
33
+ export declare const GitHubMark: ConnectorLogo;
34
34
  /** Microsoft — the four squares. */
35
35
  export declare function MicrosoftMark({ colored, className }: ConnectorLogoProps): import("react").JSX.Element;
@@ -1,4 +1,5 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Github } from "@ai-matrx/icons/brand";
2
3
  import { cn } from "@ai-matrx/design-system";
3
4
  function MarkSvg({
4
5
  className,
@@ -195,15 +196,7 @@ function GoogleSheetsMark({ colored = false, className }) {
195
196
  )
196
197
  ] });
197
198
  }
198
- function GitHubMark({ className }) {
199
- return /* @__PURE__ */ jsx(MarkSvg, { className, children: /* @__PURE__ */ jsx(
200
- "path",
201
- {
202
- fill: "currentColor",
203
- d: "M12 .3a12 12 0 0 0-3.8 23.38c.6.12.83-.26.83-.57L9 21.07c-3.34.72-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.08-.74.09-.73.09-.73 1.2.09 1.83 1.24 1.83 1.24 1.07 1.83 2.81 1.3 3.5 1 .1-.78.42-1.31.76-1.61-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.14-.3-.54-1.52.1-3.18 0 0 1-.32 3.3 1.23a11.5 11.5 0 0 1 6 0c2.28-1.55 3.29-1.23 3.29-1.23.64 1.66.24 2.88.12 3.18a4.65 4.65 0 0 1 1.23 3.22c0 4.61-2.8 5.63-5.48 5.92.42.36.81 1.1.81 2.22l-.01 3.29c0 .31.2.69.82.57A12 12 0 0 0 12 .3Z"
204
- }
205
- ) });
206
- }
199
+ const GitHubMark = lucideMark(Github);
207
200
  function MicrosoftMark({ colored = false, className }) {
208
201
  const c = (hex) => colored ? hex : "currentColor";
209
202
  return /* @__PURE__ */ jsxs(MarkSvg, { className, children: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/chat",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "THE one AI Matrx person-to-agent chat system: the full chat (running agents, conversations, tool calls, streaming, context, surfaces, voice) as per-module subpaths @ai-matrx/chat/<domain>/<module> over a host contract (/host, /host/react) whose only required value is a db; plus the kernel compatibility entries (., /external-store, /matrx, /react, /tools, styles.css, tokens.css) until P28.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -201,21 +201,21 @@
201
201
  "server-only": "^0.0.1",
202
202
  "uuid": "^14.0.2",
203
203
  "zod": "^4.6.5",
204
- "@ai-matrx/agents": "latest",
205
204
  "@ai-matrx/alchemy": "latest",
205
+ "@ai-matrx/associations": "latest",
206
+ "@ai-matrx/browser-audio": "latest",
206
207
  "@ai-matrx/canvas": "latest",
207
208
  "@ai-matrx/content-ir": "latest",
208
- "@ai-matrx/browser-audio": "latest",
209
- "@ai-matrx/associations": "latest",
209
+ "@ai-matrx/agents": "latest",
210
210
  "@ai-matrx/content-ir-react": "latest",
211
211
  "@ai-matrx/data": "latest",
212
- "@ai-matrx/diff": "latest",
212
+ "@ai-matrx/design-system": "latest",
213
213
  "@ai-matrx/icons": "latest",
214
214
  "@ai-matrx/kit": "latest",
215
215
  "@ai-matrx/media": "latest",
216
+ "@ai-matrx/diff": "latest",
216
217
  "@ai-matrx/print": "latest",
217
218
  "@ai-matrx/realtime": "latest",
218
- "@ai-matrx/design-system": "latest",
219
219
  "@ai-matrx/records": "latest",
220
220
  "@ai-matrx/records-ui": "latest",
221
221
  "@ai-matrx/tap-target": "latest"