@ai-matrx/chat 0.3.19 → 0.3.21

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @ai-matrx/chat
2
2
 
3
+ ## 0.3.21 — 2026-10-07
4
+ ### The editable definition leaves chat (AGENT-CORE-PLAN B2, builder tier)
5
+
6
+ - Removed from `agents/redux/agent-definition/slice`: the 22 edit reducers (`setAgentField`, `setAgentMessages`, `setAgentSettings`, `setAgentVariableDefinitions`, `setAgentControlBinding`, `setAgentContextPolicies`, `setAgentTools`, `setAgentCustomTools`, `setAgentMcpServers`, `setAgentSkillConfig`, `setAgentModelTiers`, `setAgentOutputSchema`, `setAgentUiGates`, `setAgentMatrxDirectives`, `resetAgentField`, `resetAllAgentFields`, `markAgentSaved`, `markAgentFieldSaved`, `rollbackAgentOptimisticUpdate`, `undoAgentEdit`, `redoAgentEdit`, `clearAgentUndoHistory`) and the undo/redo machinery. Removed from `.../thunks`: `fetchFullAgent` (a run reads the run tier only).
7
+ - Added: `markAgentRecordClean(record)` (slice) — the one "clean baseline" helper, shared by `upsertAgent` and the host's builder.
8
+ - Guard G2 (`check:no-builder-tier`) is now a hard ban: 0 builder-tier references in chat source, no baseline.
9
+ - Consumer action: a host with an agent builder owns the edits. matrx-frontend: `features/agents/redux/agent-builder.slice` (actions `agentBuilder/*`, composed onto the `agentDefinition` key with `withAgentBuilderEdits(chatReducers.agentDefinition)`) and `features/agents/redux/fetch-full-agent.thunk`. A host without a builder: none.
10
+
11
+ ## 0.3.20 — 2026-10-07
12
+ ### Working-document realtime channel renamed
13
+
14
+ - The working-document row channel is `working-document-row` (was `cx-working-doc`). 0.3.19 changed that namespace's description in place, and a dev server that had loaded 0.3.15 refused the new declaration on every page ("cannot be re-declared"). A fresh process was never affected.
15
+ - Consumer action: none.
16
+
3
17
  ## 0.3.19 — 2026-10-07
4
18
  ### Working documents fix-up
5
19
 
@@ -82,7 +82,10 @@ import { selectUserId } from "../../host/identity.js";
82
82
  const AUTOSAVE_MS = 700;
83
83
  const wdChannels = /* @__PURE__ */ new Map();
84
84
  const workingDocChannel = defineChannelNamespace({
85
- namespace: "cx-working-doc",
85
+ // Renamed from "cx-working-doc" when the row moved to content.document (C5c):
86
+ // a namespace whose spec changes must take a new name, or a long-lived dev
87
+ // server that still holds the old declaration refuses the new one.
88
+ namespace: "working-document-row",
86
89
  parts: ["documentId"],
87
90
  description: "content.document row for one chat working document"
88
91
  });
@@ -1,5 +1,10 @@
1
1
  import { type PayloadAction } from "@reduxjs/toolkit";
2
- import type { AgentDefinition, AgentDefinitionSliceState, AgentFetchStatus, FieldSnapshot, LoadedFields } from "../../types/agent-definition.types.js";
2
+ import type { AgentDefinition, AgentDefinitionRecord, AgentDefinitionSliceState, AgentFetchStatus, LoadedFields } from "../../types/agent-definition.types.js";
3
+ /**
4
+ * Marks a record as clean. Clears dirty state, field history, and undo stacks.
5
+ * _loadedFields is NOT cleared — fetched state is cumulative.
6
+ */
7
+ export declare function markAgentRecordClean(record: AgentDefinitionRecord): void;
3
8
  export declare const agentDefinitionSlice: import("@reduxjs/toolkit").Slice<AgentDefinitionSliceState, {
4
9
  /**
5
10
  * Upserts a fully-fetched agent (live or version) and marks it clean.
@@ -28,134 +33,6 @@ export declare const agentDefinitionSlice: import("@reduxjs/toolkit").Slice<Agen
28
33
  mergePartialAgent(state: AgentDefinitionSliceState, action: PayloadAction<Partial<AgentDefinition> & {
29
34
  id: string;
30
35
  }>): void;
31
- /** Edit any single field by name. For scalars and simple values. */
32
- setAgentField(state: AgentDefinitionSliceState, action: PayloadAction<{
33
- id: string;
34
- field: keyof AgentDefinition;
35
- value: AgentDefinition[keyof AgentDefinition];
36
- }>): void;
37
- setAgentMessages(state: AgentDefinitionSliceState, action: PayloadAction<{
38
- id: string;
39
- messages: AgentDefinition["messages"];
40
- }>): void;
41
- setAgentSettings(state: AgentDefinitionSliceState, action: PayloadAction<{
42
- id: string;
43
- settings: AgentDefinition["settings"];
44
- }>): void;
45
- setAgentVariableDefinitions(state: AgentDefinitionSliceState, action: PayloadAction<{
46
- id: string;
47
- variableDefinitions: AgentDefinition["variableDefinitions"];
48
- }>): void;
49
- /**
50
- * Bind or unbind a model control to a variable in ONE edit (one undo step):
51
- * the settings literal and the variable definitions move together.
52
- * See features/agents/utils/control-variables.ts.
53
- */
54
- setAgentControlBinding(state: AgentDefinitionSliceState, action: PayloadAction<{
55
- id: string;
56
- settings: AgentDefinition["settings"];
57
- variableDefinitions: AgentDefinition["variableDefinitions"];
58
- }>): void;
59
- setAgentContextPolicies(state: AgentDefinitionSliceState, action: PayloadAction<{
60
- id: string;
61
- contextPolicies: AgentDefinition["contextPolicies"];
62
- }>): void;
63
- setAgentTools(state: AgentDefinitionSliceState, action: PayloadAction<{
64
- id: string;
65
- tools: AgentDefinition["tools"];
66
- }>): void;
67
- setAgentCustomTools(state: AgentDefinitionSliceState, action: PayloadAction<{
68
- id: string;
69
- customTools: AgentDefinition["customTools"];
70
- }>): void;
71
- setAgentMcpServers(state: AgentDefinitionSliceState, action: PayloadAction<{
72
- id: string;
73
- mcpServers: AgentDefinition["mcpServers"];
74
- }>): void;
75
- /**
76
- * Per-agent skill exposure config. The full SkillConfig object is
77
- * replaced atomically; the picker UI computes the next value and
78
- * dispatches once. Marked dirty so the next save flushes it through
79
- * to `agent.definition.skill_config`.
80
- */
81
- setAgentSkillConfig(state: AgentDefinitionSliceState, action: PayloadAction<{
82
- id: string;
83
- skillConfig: AgentDefinition["skillConfig"];
84
- }>): void;
85
- setAgentModelTiers(state: AgentDefinitionSliceState, action: PayloadAction<{
86
- id: string;
87
- modelTiers: AgentDefinition["modelTiers"];
88
- }>): void;
89
- setAgentOutputSchema(state: AgentDefinitionSliceState, action: PayloadAction<{
90
- id: string;
91
- outputSchema: AgentDefinition["outputSchema"];
92
- }>): void;
93
- /**
94
- * Model-gated UI flags (FE-only). The full UiGates object is replaced
95
- * atomically; the gate editor computes the next value and dispatches once.
96
- * Persisted to `agent.definition.ui_gates` — never sent to the server.
97
- */
98
- setAgentUiGates(state: AgentDefinitionSliceState, action: PayloadAction<{
99
- id: string;
100
- uiGates: AgentDefinition["uiGates"];
101
- }>): void;
102
- /**
103
- * Matrx Directives apply config. Replaced atomically by the Matrx Directives tab;
104
- * persisted to `agent.definition.matrx_actions`. Read by aidream's output-directive
105
- * dispatcher (full rebrand of the retired settings["output_apply"]).
106
- */
107
- setAgentMatrxDirectives(state: AgentDefinitionSliceState, action: PayloadAction<{
108
- id: string;
109
- matrxDirectives: AgentDefinition["matrxDirectives"];
110
- }>): void;
111
- /** Reset one field to its original value from _fieldHistory. */
112
- resetAgentField(state: AgentDefinitionSliceState, action: PayloadAction<{
113
- id: string;
114
- field: keyof AgentDefinition;
115
- }>): void;
116
- /** Reset ALL dirty fields to their original values. No refetch needed. */
117
- resetAllAgentFields(state: AgentDefinitionSliceState, action: PayloadAction<{
118
- id: string;
119
- }>): void;
120
- /** Called after a successful save. Current values become the new clean baseline. */
121
- markAgentSaved(state: AgentDefinitionSliceState, action: PayloadAction<{
122
- id: string;
123
- }>): void;
124
- /**
125
- * A SINGLE field was written to the database (inline edits such as the
126
- * Agent Info Editor's name). Only that field becomes the clean baseline —
127
- * every other staged edit (e.g. an Output Schema kind binding applied in
128
- * Model Settings) must stay dirty so the main Save still persists it.
129
- * `markAgentSaved` here used to wipe every dirty flag, silently dropping
130
- * unrelated staged edits (feedback 222a2925).
131
- */
132
- markAgentFieldSaved(state: AgentDefinitionSliceState, action: PayloadAction<{
133
- id: string;
134
- field: keyof AgentDefinition;
135
- }>): void;
136
- /** Save failed — restore from the snapshot taken before the optimistic write. */
137
- rollbackAgentOptimisticUpdate(state: AgentDefinitionSliceState, action: PayloadAction<{
138
- id: string;
139
- snapshot: FieldSnapshot;
140
- }>): void;
141
- /**
142
- * Pops the most recent entry from _undoPast, pushes current value to
143
- * _undoFuture, and restores the previous value. Also maintains dirty tracking.
144
- */
145
- undoAgentEdit(state: AgentDefinitionSliceState, action: PayloadAction<{
146
- id: string;
147
- }>): void;
148
- /**
149
- * Pops the most recent entry from _undoFuture, pushes current value to
150
- * _undoPast, and applies the redo value. Also maintains dirty tracking.
151
- */
152
- redoAgentEdit(state: AgentDefinitionSliceState, action: PayloadAction<{
153
- id: string;
154
- }>): void;
155
- /** Clears the undo/redo stacks without affecting the current state. */
156
- clearAgentUndoHistory(state: AgentDefinitionSliceState, action: PayloadAction<{
157
- id: string;
158
- }>): void;
159
36
  markAgentFieldsLoaded(state: AgentDefinitionSliceState, action: PayloadAction<{
160
37
  id: string;
161
38
  fields: (keyof AgentDefinition)[];
@@ -188,70 +65,7 @@ export declare const upsertAgent: import("@reduxjs/toolkit").ActionCreatorWithPa
188
65
  id: string;
189
66
  }, "agentDefinition/seedAgentFromTemplate">, mergePartialAgent: import("@reduxjs/toolkit").ActionCreatorWithPayload<Partial<AgentDefinition> & {
190
67
  id: string;
191
- }, "agentDefinition/mergePartialAgent">, setAgentField: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
192
- id: string;
193
- field: keyof AgentDefinition;
194
- value: AgentDefinition[keyof AgentDefinition];
195
- }, "agentDefinition/setAgentField">, setAgentMessages: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
196
- id: string;
197
- messages: AgentDefinition["messages"];
198
- }, "agentDefinition/setAgentMessages">, setAgentSettings: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
199
- id: string;
200
- settings: AgentDefinition["settings"];
201
- }, "agentDefinition/setAgentSettings">, setAgentVariableDefinitions: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
202
- id: string;
203
- variableDefinitions: AgentDefinition["variableDefinitions"];
204
- }, "agentDefinition/setAgentVariableDefinitions">, setAgentControlBinding: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
205
- id: string;
206
- settings: AgentDefinition["settings"];
207
- variableDefinitions: AgentDefinition["variableDefinitions"];
208
- }, "agentDefinition/setAgentControlBinding">, setAgentContextPolicies: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
209
- id: string;
210
- contextPolicies: AgentDefinition["contextPolicies"];
211
- }, "agentDefinition/setAgentContextPolicies">, setAgentTools: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
212
- id: string;
213
- tools: AgentDefinition["tools"];
214
- }, "agentDefinition/setAgentTools">, setAgentCustomTools: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
215
- id: string;
216
- customTools: AgentDefinition["customTools"];
217
- }, "agentDefinition/setAgentCustomTools">, setAgentMcpServers: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
218
- id: string;
219
- mcpServers: AgentDefinition["mcpServers"];
220
- }, "agentDefinition/setAgentMcpServers">, setAgentSkillConfig: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
221
- id: string;
222
- skillConfig: AgentDefinition["skillConfig"];
223
- }, "agentDefinition/setAgentSkillConfig">, setAgentModelTiers: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
224
- id: string;
225
- modelTiers: AgentDefinition["modelTiers"];
226
- }, "agentDefinition/setAgentModelTiers">, setAgentOutputSchema: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
227
- id: string;
228
- outputSchema: AgentDefinition["outputSchema"];
229
- }, "agentDefinition/setAgentOutputSchema">, setAgentUiGates: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
230
- id: string;
231
- uiGates: AgentDefinition["uiGates"];
232
- }, "agentDefinition/setAgentUiGates">, setAgentMatrxDirectives: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
233
- id: string;
234
- matrxDirectives: AgentDefinition["matrxDirectives"];
235
- }, "agentDefinition/setAgentMatrxDirectives">, resetAgentField: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
236
- id: string;
237
- field: keyof AgentDefinition;
238
- }, "agentDefinition/resetAgentField">, resetAllAgentFields: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
239
- id: string;
240
- }, "agentDefinition/resetAllAgentFields">, markAgentSaved: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
241
- id: string;
242
- }, "agentDefinition/markAgentSaved">, markAgentFieldSaved: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
243
- id: string;
244
- field: keyof AgentDefinition;
245
- }, "agentDefinition/markAgentFieldSaved">, rollbackAgentOptimisticUpdate: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
246
- id: string;
247
- snapshot: FieldSnapshot;
248
- }, "agentDefinition/rollbackAgentOptimisticUpdate">, undoAgentEdit: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
249
- id: string;
250
- }, "agentDefinition/undoAgentEdit">, redoAgentEdit: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
251
- id: string;
252
- }, "agentDefinition/redoAgentEdit">, clearAgentUndoHistory: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
253
- id: string;
254
- }, "agentDefinition/clearAgentUndoHistory">, markAgentFieldsLoaded: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
68
+ }, "agentDefinition/mergePartialAgent">, markAgentFieldsLoaded: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
255
69
  id: string;
256
70
  fields: (keyof AgentDefinition)[];
257
71
  }, "agentDefinition/markAgentFieldsLoaded">, setAgentFetchStatus: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
@@ -1,19 +1,12 @@
1
1
  import { createSlice } from "@reduxjs/toolkit";
2
- import isEqual from "lodash/isEqual";
3
2
  import { isJsonObject } from "../../../utils/json.js";
4
3
  import {
5
- shouldUpgradeFetchStatus,
6
- UNDO_MAX_ENTRIES,
7
- UNDO_MAX_BYTES,
8
- UNDO_COALESCE_MS
4
+ shouldUpgradeFetchStatus
9
5
  } from "../../types/agent-definition.types.js";
10
6
  import {
11
7
  addField,
12
8
  assignField,
13
- createFieldFlags,
14
- fieldFlagsSize,
15
- hasField,
16
- removeField
9
+ createFieldFlags
17
10
  } from "@ai-matrx/agents/field-flags";
18
11
  function makeEmptyRecord(id) {
19
12
  return {
@@ -115,89 +108,7 @@ function mergeAndTrack(record, partial) {
115
108
  }
116
109
  });
117
110
  }
118
- function estimateBytes(value) {
119
- if (value == null) return 8;
120
- if (typeof value === "string") return value.length * 2;
121
- if (typeof value === "number" || typeof value === "boolean") return 8;
122
- try {
123
- return JSON.stringify(value).length * 2;
124
- } catch {
125
- return 1024;
126
- }
127
- }
128
- function totalStackBytes(stack) {
129
- let total = 0;
130
- for (const entry of stack) total += entry.byteEstimate;
131
- return total;
132
- }
133
- function compressStack(stack) {
134
- if (stack.length <= UNDO_MAX_ENTRIES && totalStackBytes(stack) <= UNDO_MAX_BYTES) {
135
- return stack;
136
- }
137
- const protectedHead = 1;
138
- const protectedTail = Math.min(20, Math.floor(stack.length * 0.4));
139
- if (stack.length <= protectedHead + protectedTail) return stack;
140
- const head = stack.slice(0, protectedHead);
141
- const tail = stack.slice(-protectedTail);
142
- let middle = stack.slice(protectedHead, stack.length - protectedTail);
143
- const merged = [];
144
- for (const entry of middle) {
145
- const prev = merged[merged.length - 1];
146
- if (prev && prev.field === entry.field) {
147
- continue;
148
- }
149
- merged.push(entry);
150
- }
151
- middle = merged;
152
- let result = [...head, ...middle, ...tail];
153
- if (result.length > UNDO_MAX_ENTRIES) {
154
- const thinned = [];
155
- for (let i = 0; i < middle.length; i++) {
156
- if (i % 2 === 0) thinned.push(middle[i]);
157
- }
158
- result = [...head, ...thinned, ...tail];
159
- }
160
- while (result.length > protectedHead + protectedTail + 1 && totalStackBytes(result) > UNDO_MAX_BYTES) {
161
- result.splice(protectedHead, 1);
162
- }
163
- return result;
164
- }
165
- function pushUndoEntry(record, field, previousValue) {
166
- const now = Date.now();
167
- const bytes = estimateBytes(previousValue);
168
- const top = record._undoPast[record._undoPast.length - 1];
169
- if (top && top.field === field && now - top.timestamp < UNDO_COALESCE_MS) {
170
- top.timestamp = now;
171
- } else {
172
- record._undoPast.push({
173
- field,
174
- value: previousValue,
175
- timestamp: now,
176
- byteEstimate: bytes
177
- });
178
- }
179
- record._undoFuture = [];
180
- record._undoPast = compressStack(record._undoPast);
181
- }
182
- function applyFieldEdit(record, field, value) {
183
- const previousValue = record[field];
184
- const wasDirty = hasField(record._dirtyFields, field);
185
- if (!wasDirty && isEqual(value, previousValue)) return;
186
- if (!wasDirty) {
187
- record._fieldHistory[field] = previousValue;
188
- }
189
- pushUndoEntry(record, field, previousValue);
190
- assignField(record, field, value);
191
- const baseline = record._fieldHistory[field];
192
- if (wasDirty && isEqual(value, baseline)) {
193
- removeField(record._dirtyFields, field);
194
- delete record._fieldHistory[field];
195
- } else {
196
- addField(record._dirtyFields, field);
197
- }
198
- record._dirty = fieldFlagsSize(record._dirtyFields) > 0;
199
- }
200
- function markRecordClean(record) {
111
+ function markAgentRecordClean(record) {
201
112
  record._dirty = false;
202
113
  record._dirtyFields = createFieldFlags();
203
114
  record._fieldHistory = {};
@@ -241,7 +152,7 @@ const agentDefinitionSlice = createSlice({
241
152
  const normalizedData = messagesWereNormalized ? { ...withStar, messages: normalizedMessages } : withStar;
242
153
  if (existing) {
243
154
  mergeAndTrack(existing, normalizedData);
244
- markRecordClean(existing);
155
+ markAgentRecordClean(existing);
245
156
  if (messagesWereNormalized) {
246
157
  addField(existing._dirtyFields, "messages");
247
158
  existing._dirty = true;
@@ -250,7 +161,7 @@ const agentDefinitionSlice = createSlice({
250
161
  } else {
251
162
  const record = makeEmptyRecord(data.id);
252
163
  mergeAndTrack(record, normalizedData);
253
- markRecordClean(record);
164
+ markAgentRecordClean(record);
254
165
  if (messagesWereNormalized) {
255
166
  addField(record._dirtyFields, "messages");
256
167
  record._dirty = true;
@@ -325,7 +236,7 @@ const agentDefinitionSlice = createSlice({
325
236
  if (!state.agents[full.id]) {
326
237
  const record = makeEmptyRecord(full.id);
327
238
  mergeAndTrack(record, full);
328
- markRecordClean(record);
239
+ markAgentRecordClean(record);
329
240
  if (seedMessagesNormalized) {
330
241
  addField(record._dirtyFields, "messages");
331
242
  record._dirty = true;
@@ -355,229 +266,6 @@ const agentDefinitionSlice = createSlice({
355
266
  state.agents[data.id] = record;
356
267
  }
357
268
  },
358
- // ── User field edits (trigger dirty) ─────────────────────────────────────
359
- /** Edit any single field by name. For scalars and simple values. */
360
- setAgentField(state, action) {
361
- const { id, field, value } = action.payload;
362
- const record = state.agents[id];
363
- if (!record) return;
364
- applyFieldEdit(record, field, value);
365
- },
366
- // ── Dedicated actions for complex fields ──────────────────────────────────
367
- setAgentMessages(state, action) {
368
- const record = state.agents[action.payload.id];
369
- if (!record) return;
370
- applyFieldEdit(record, "messages", action.payload.messages);
371
- },
372
- setAgentSettings(state, action) {
373
- const record = state.agents[action.payload.id];
374
- if (!record) return;
375
- applyFieldEdit(record, "settings", action.payload.settings);
376
- },
377
- setAgentVariableDefinitions(state, action) {
378
- const record = state.agents[action.payload.id];
379
- if (!record) return;
380
- applyFieldEdit(
381
- record,
382
- "variableDefinitions",
383
- action.payload.variableDefinitions
384
- );
385
- },
386
- /**
387
- * Bind or unbind a model control to a variable in ONE edit (one undo step):
388
- * the settings literal and the variable definitions move together.
389
- * See features/agents/utils/control-variables.ts.
390
- */
391
- setAgentControlBinding(state, action) {
392
- const record = state.agents[action.payload.id];
393
- if (!record) return;
394
- applyFieldEdit(record, "settings", action.payload.settings);
395
- applyFieldEdit(
396
- record,
397
- "variableDefinitions",
398
- action.payload.variableDefinitions
399
- );
400
- },
401
- setAgentContextPolicies(state, action) {
402
- const record = state.agents[action.payload.id];
403
- if (!record) return;
404
- applyFieldEdit(record, "contextPolicies", action.payload.contextPolicies);
405
- },
406
- setAgentTools(state, action) {
407
- const record = state.agents[action.payload.id];
408
- if (!record) return;
409
- applyFieldEdit(record, "tools", action.payload.tools);
410
- },
411
- setAgentCustomTools(state, action) {
412
- const record = state.agents[action.payload.id];
413
- if (!record) return;
414
- applyFieldEdit(record, "customTools", action.payload.customTools);
415
- },
416
- setAgentMcpServers(state, action) {
417
- const record = state.agents[action.payload.id];
418
- if (!record) return;
419
- applyFieldEdit(record, "mcpServers", action.payload.mcpServers);
420
- },
421
- /**
422
- * Per-agent skill exposure config. The full SkillConfig object is
423
- * replaced atomically; the picker UI computes the next value and
424
- * dispatches once. Marked dirty so the next save flushes it through
425
- * to `agent.definition.skill_config`.
426
- */
427
- setAgentSkillConfig(state, action) {
428
- const record = state.agents[action.payload.id];
429
- if (!record) return;
430
- applyFieldEdit(record, "skillConfig", action.payload.skillConfig);
431
- },
432
- setAgentModelTiers(state, action) {
433
- const record = state.agents[action.payload.id];
434
- if (!record) return;
435
- applyFieldEdit(record, "modelTiers", action.payload.modelTiers);
436
- },
437
- setAgentOutputSchema(state, action) {
438
- const record = state.agents[action.payload.id];
439
- if (!record) return;
440
- applyFieldEdit(record, "outputSchema", action.payload.outputSchema);
441
- },
442
- /**
443
- * Model-gated UI flags (FE-only). The full UiGates object is replaced
444
- * atomically; the gate editor computes the next value and dispatches once.
445
- * Persisted to `agent.definition.ui_gates` — never sent to the server.
446
- */
447
- setAgentUiGates(state, action) {
448
- const record = state.agents[action.payload.id];
449
- if (!record) return;
450
- applyFieldEdit(record, "uiGates", action.payload.uiGates);
451
- },
452
- /**
453
- * Matrx Directives apply config. Replaced atomically by the Matrx Directives tab;
454
- * persisted to `agent.definition.matrx_actions`. Read by aidream's output-directive
455
- * dispatcher (full rebrand of the retired settings["output_apply"]).
456
- */
457
- setAgentMatrxDirectives(state, action) {
458
- const record = state.agents[action.payload.id];
459
- if (!record) return;
460
- applyFieldEdit(record, "matrxDirectives", action.payload.matrxDirectives);
461
- },
462
- // ── Dirty / history management ────────────────────────────────────────────
463
- /** Reset one field to its original value from _fieldHistory. */
464
- resetAgentField(state, action) {
465
- const { id, field } = action.payload;
466
- const record = state.agents[id];
467
- if (!record || !hasField(record._dirtyFields, field)) return;
468
- const original = record._fieldHistory[field];
469
- if (original !== void 0) {
470
- assignField(record, field, original);
471
- }
472
- removeField(record._dirtyFields, field);
473
- delete record._fieldHistory[field];
474
- record._dirty = fieldFlagsSize(record._dirtyFields) > 0;
475
- },
476
- /** Reset ALL dirty fields to their original values. No refetch needed. */
477
- resetAllAgentFields(state, action) {
478
- const record = state.agents[action.payload.id];
479
- if (!record) return;
480
- Object.keys(record._fieldHistory).forEach(
481
- (field) => {
482
- const original = record._fieldHistory[field];
483
- if (original !== void 0) {
484
- assignField(record, field, original);
485
- }
486
- }
487
- );
488
- markRecordClean(record);
489
- },
490
- /** Called after a successful save. Current values become the new clean baseline. */
491
- markAgentSaved(state, action) {
492
- const record = state.agents[action.payload.id];
493
- if (!record) return;
494
- markRecordClean(record);
495
- },
496
- /**
497
- * A SINGLE field was written to the database (inline edits such as the
498
- * Agent Info Editor's name). Only that field becomes the clean baseline —
499
- * every other staged edit (e.g. an Output Schema kind binding applied in
500
- * Model Settings) must stay dirty so the main Save still persists it.
501
- * `markAgentSaved` here used to wipe every dirty flag, silently dropping
502
- * unrelated staged edits (feedback 222a2925).
503
- */
504
- markAgentFieldSaved(state, action) {
505
- const { id, field } = action.payload;
506
- const record = state.agents[id];
507
- if (!record) return;
508
- removeField(record._dirtyFields, field);
509
- delete record._fieldHistory[field];
510
- record._undoPast = record._undoPast.filter((e) => e.field !== field);
511
- record._undoFuture = record._undoFuture.filter((e) => e.field !== field);
512
- record._dirty = fieldFlagsSize(record._dirtyFields) > 0;
513
- },
514
- /** Save failed — restore from the snapshot taken before the optimistic write. */
515
- rollbackAgentOptimisticUpdate(state, action) {
516
- const { id, snapshot } = action.payload;
517
- const record = state.agents[id];
518
- if (!record) return;
519
- Object.keys(snapshot).forEach((field) => {
520
- const value = snapshot[field];
521
- if (value !== void 0) {
522
- assignField(record, field, value);
523
- }
524
- });
525
- record._dirty = fieldFlagsSize(record._dirtyFields) > 0;
526
- },
527
- // ── Undo / Redo ──────────────────────────────────────────────────────────
528
- /**
529
- * Pops the most recent entry from _undoPast, pushes current value to
530
- * _undoFuture, and restores the previous value. Also maintains dirty tracking.
531
- */
532
- undoAgentEdit(state, action) {
533
- const record = state.agents[action.payload.id];
534
- if (!record || record._undoPast.length === 0) return;
535
- const entry = record._undoPast.pop();
536
- if (!entry) return;
537
- const currentValue = record[entry.field];
538
- record._undoFuture.push({
539
- field: entry.field,
540
- value: currentValue,
541
- timestamp: Date.now(),
542
- byteEstimate: estimateBytes(currentValue)
543
- });
544
- assignField(record, entry.field, entry.value);
545
- const originalValue = record._fieldHistory[entry.field];
546
- if (originalValue !== void 0 && isEqual(entry.value, originalValue)) {
547
- removeField(record._dirtyFields, entry.field);
548
- delete record._fieldHistory[entry.field];
549
- } else if (!hasField(record._dirtyFields, entry.field)) {
550
- addField(record._dirtyFields, entry.field);
551
- }
552
- record._dirty = fieldFlagsSize(record._dirtyFields) > 0;
553
- },
554
- /**
555
- * Pops the most recent entry from _undoFuture, pushes current value to
556
- * _undoPast, and applies the redo value. Also maintains dirty tracking.
557
- */
558
- redoAgentEdit(state, action) {
559
- const record = state.agents[action.payload.id];
560
- if (!record || record._undoFuture.length === 0) return;
561
- const entry = record._undoFuture.pop();
562
- if (!entry) return;
563
- const currentValue = record[entry.field];
564
- record._undoPast.push({
565
- field: entry.field,
566
- value: currentValue,
567
- timestamp: Date.now(),
568
- byteEstimate: estimateBytes(currentValue)
569
- });
570
- assignField(record, entry.field, entry.value);
571
- addField(record._dirtyFields, entry.field);
572
- record._dirty = true;
573
- },
574
- /** Clears the undo/redo stacks without affecting the current state. */
575
- clearAgentUndoHistory(state, action) {
576
- const record = state.agents[action.payload.id];
577
- if (!record) return;
578
- record._undoPast = [];
579
- record._undoFuture = [];
580
- },
581
269
  // ── Explicitly mark fields as loaded (without writing data) ──────────────
582
270
  markAgentFieldsLoaded(state, action) {
583
271
  const record = state.agents[action.payload.id];
@@ -637,28 +325,6 @@ const {
637
325
  upsertAgent,
638
326
  seedAgentFromTemplate,
639
327
  mergePartialAgent,
640
- setAgentField,
641
- setAgentMessages,
642
- setAgentSettings,
643
- setAgentVariableDefinitions,
644
- setAgentControlBinding,
645
- setAgentContextPolicies,
646
- setAgentTools,
647
- setAgentCustomTools,
648
- setAgentMcpServers,
649
- setAgentSkillConfig,
650
- setAgentModelTiers,
651
- setAgentOutputSchema,
652
- setAgentUiGates,
653
- setAgentMatrxDirectives,
654
- resetAgentField,
655
- resetAllAgentFields,
656
- markAgentSaved,
657
- markAgentFieldSaved,
658
- rollbackAgentOptimisticUpdate,
659
- undoAgentEdit,
660
- redoAgentEdit,
661
- clearAgentUndoHistory,
662
328
  markAgentFieldsLoaded,
663
329
  setAgentFetchStatus,
664
330
  setAgentLoading,
@@ -672,39 +338,18 @@ const {
672
338
  var slice_default = agentDefinitionSlice.reducer;
673
339
  export {
674
340
  agentDefinitionSlice,
675
- clearAgentUndoHistory,
676
341
  slice_default as default,
677
- markAgentFieldSaved,
678
342
  markAgentFieldsLoaded,
679
- markAgentSaved,
343
+ markAgentRecordClean,
680
344
  mergePartialAgent,
681
- redoAgentEdit,
682
345
  removeAgent,
683
346
  removeVersionsByParentId,
684
- resetAgentField,
685
- resetAllAgentFields,
686
- rollbackAgentOptimisticUpdate,
687
347
  seedAgentFromTemplate,
688
348
  setActiveAgentId,
689
- setAgentContextPolicies,
690
- setAgentControlBinding,
691
- setAgentCustomTools,
692
349
  setAgentError,
693
350
  setAgentFetchStatus,
694
- setAgentField,
695
351
  setAgentLoading,
696
- setAgentMatrxDirectives,
697
- setAgentMcpServers,
698
- setAgentMessages,
699
- setAgentModelTiers,
700
- setAgentOutputSchema,
701
- setAgentSettings,
702
- setAgentSkillConfig,
703
- setAgentTools,
704
- setAgentUiGates,
705
- setAgentVariableDefinitions,
706
352
  setAgentsError,
707
353
  setAgentsStatus,
708
- undoAgentEdit,
709
354
  upsertAgent
710
355
  };
@@ -227,21 +227,6 @@ export declare const fetchAgentRunControls: import("@reduxjs/toolkit").AsyncThun
227
227
  dispatch: ChatDispatch;
228
228
  state: ChatRootState;
229
229
  }>;
230
- /**
231
- * Fetches the complete agent row via PostgREST and upserts it into state.
232
- * Marks the record fully clean — all fields tracked as loaded.
233
- * Use this when opening the agent builder or after creating/duplicating an agent.
234
- */
235
- export declare const fetchFullAgent: import("@reduxjs/toolkit").AsyncThunk<void, string, {
236
- extra?: unknown;
237
- rejectValue?: unknown;
238
- serializedErrorType?: unknown;
239
- pendingMeta?: unknown;
240
- fulfilledMeta?: unknown;
241
- rejectedMeta?: unknown;
242
- dispatch: ChatDispatch;
243
- state: ChatRootState;
244
- }>;
245
230
  export interface SharedAgentItem {
246
231
  id: string;
247
232
  name: string;
@@ -30,7 +30,6 @@ import {
30
30
  readFavoriteIds
31
31
  } from "../../../context/sources/scopes.js";
32
32
  import {
33
- upsertAgent,
34
33
  mergePartialAgent,
35
34
  setAgentFetchStatus,
36
35
  setAgentLoading,
@@ -43,7 +42,6 @@ import {
43
42
  selectAgentRunControlsReady
44
43
  } from "./selectors.js";
45
44
  import {
46
- dbRowToAgentDefinition,
47
45
  parseSkillConfigJson
48
46
  } from "./converters.js";
49
47
  import {
@@ -362,20 +360,6 @@ const fetchAgentRunControls = createAsyncThunk(
362
360
  );
363
361
  }
364
362
  );
365
- const fetchFullAgent = createAsyncThunk(
366
- "agentDefinition/fetchFull",
367
- async (agentId, { dispatch }) => {
368
- dispatch(setAgentLoading({ id: agentId, loading: true }));
369
- const { data, error } = await supabase.schema("agent").from("definition").select("*").eq("id", agentId).single();
370
- dispatch(setAgentLoading({ id: agentId, loading: false }));
371
- if (error) {
372
- dispatch(setAgentError({ id: agentId, error: error.message }));
373
- throw agentNameTakenError(error) ?? pgErrorToError(error);
374
- }
375
- dispatch(upsertAgent(dbRowToAgentDefinition(data)));
376
- await overlayAgentFavorites(dispatch, [agentId]);
377
- }
378
- );
379
363
  const fetchSharedAgents = createAsyncThunk("agentDefinition/fetchShared", async (_, { dispatch }) => {
380
364
  const { data, error } = await supabase.rpc("agx_get_shared_with_me");
381
365
  if (error) throw pgErrorToError(error);
@@ -455,7 +439,6 @@ export {
455
439
  fetchAgentRunTier,
456
440
  fetchAgentsList,
457
441
  fetchAgentsListFull,
458
- fetchFullAgent,
459
442
  fetchSharedAgents,
460
443
  fetchSharedAgentsForChat,
461
444
  initializeChatAgents,
@@ -10184,6 +10184,113 @@ export type ChatDatabase = {
10184
10184
  }
10185
10185
  ];
10186
10186
  };
10187
+ udt_dataset_template_fields: {
10188
+ Row: {
10189
+ created_at: string;
10190
+ custom_fields: Json;
10191
+ data_type: Database["public"]["Enums"]["field_data_type"];
10192
+ default_value: Json | null;
10193
+ display_name: string;
10194
+ field_name: string;
10195
+ field_order: number;
10196
+ id: string;
10197
+ is_required: boolean;
10198
+ metadata: Json;
10199
+ organization_id: string;
10200
+ template_id: string;
10201
+ updated_at: string;
10202
+ validation_rules: Json | null;
10203
+ };
10204
+ Insert: {
10205
+ created_at?: string;
10206
+ custom_fields?: Json;
10207
+ data_type?: Database["public"]["Enums"]["field_data_type"];
10208
+ default_value?: Json | null;
10209
+ display_name: string;
10210
+ field_name: string;
10211
+ field_order?: number;
10212
+ id?: string;
10213
+ is_required?: boolean;
10214
+ metadata?: Json;
10215
+ organization_id: string;
10216
+ template_id: string;
10217
+ updated_at?: string;
10218
+ validation_rules?: Json | null;
10219
+ };
10220
+ Update: {
10221
+ created_at?: string;
10222
+ custom_fields?: Json;
10223
+ data_type?: Database["public"]["Enums"]["field_data_type"];
10224
+ default_value?: Json | null;
10225
+ display_name?: string;
10226
+ field_name?: string;
10227
+ field_order?: number;
10228
+ id?: string;
10229
+ is_required?: boolean;
10230
+ metadata?: Json;
10231
+ organization_id?: string;
10232
+ template_id?: string;
10233
+ updated_at?: string;
10234
+ validation_rules?: Json | null;
10235
+ };
10236
+ Relationships: [
10237
+ {
10238
+ foreignKeyName: "udt_dataset_template_fields_template_id_fkey";
10239
+ columns: ["template_id"];
10240
+ isOneToOne: false;
10241
+ referencedRelation: "udt_dataset_templates";
10242
+ referencedColumns: ["id"];
10243
+ }
10244
+ ];
10245
+ };
10246
+ udt_dataset_templates: {
10247
+ Row: {
10248
+ created_at: string;
10249
+ created_by: string | null;
10250
+ custom_fields: Json;
10251
+ deleted_at: string | null;
10252
+ description: string;
10253
+ id: string;
10254
+ is_active: boolean;
10255
+ metadata: Json;
10256
+ name: string;
10257
+ organization_id: string;
10258
+ updated_at: string;
10259
+ updated_by: string | null;
10260
+ version: number;
10261
+ };
10262
+ Insert: {
10263
+ created_at?: string;
10264
+ created_by?: string | null;
10265
+ custom_fields?: Json;
10266
+ deleted_at?: string | null;
10267
+ description?: string;
10268
+ id?: string;
10269
+ is_active?: boolean;
10270
+ metadata?: Json;
10271
+ name: string;
10272
+ organization_id: string;
10273
+ updated_at?: string;
10274
+ updated_by?: string | null;
10275
+ version?: number;
10276
+ };
10277
+ Update: {
10278
+ created_at?: string;
10279
+ created_by?: string | null;
10280
+ custom_fields?: Json;
10281
+ deleted_at?: string | null;
10282
+ description?: string;
10283
+ id?: string;
10284
+ is_active?: boolean;
10285
+ metadata?: Json;
10286
+ name?: string;
10287
+ organization_id?: string;
10288
+ updated_at?: string;
10289
+ updated_by?: string | null;
10290
+ version?: number;
10291
+ };
10292
+ Relationships: [];
10293
+ };
10187
10294
  udt_datasets: {
10188
10295
  Row: {
10189
10296
  created_at: string;
@@ -61,6 +61,12 @@ export declare function formatMetaNumber(n: number): string;
61
61
  * line still says it is there.
62
62
  */
63
63
  export declare function isNotApplicableValue(val: unknown): boolean;
64
+ /**
65
+ * The collapsed omission line NAMES what did not apply (review 8041caec,
66
+ * 2026-10-07: "1 field did not apply" left the reader guessing which). Up to
67
+ * three names are listed; past that the rest is counted.
68
+ */
69
+ export declare function notApplicableSummary(names: readonly string[]): string;
64
70
  /**
65
71
  * An object carrying its own `__kind` is a kind, not a field list: it renders
66
72
  * through the value door (`KindValueNode` → `AnswerValueView`) and the caller
@@ -118,6 +118,13 @@ const IdentifierListRow = ({
118
118
  open && /* @__PURE__ */ jsx("div", { className: "mt-1 flex flex-wrap items-center gap-1.5", children: ids.map((id, i) => /* @__PURE__ */ jsx(ShortId, { value: id }, `${id}-${i}`)) })
119
119
  ] });
120
120
  };
121
+ function notApplicableSummary(names) {
122
+ if (names.length === 0) return "";
123
+ const head = `${names.length} ${names.length === 1 ? "field" : "fields"} did not apply`;
124
+ const shown = names.slice(0, 3).join(", ");
125
+ const more = names.length - 3;
126
+ return `${head}: ${shown}${more > 0 ? ` and ${more} more` : ""}`;
127
+ }
121
128
  const NotApplicableRow = ({ entries, labels, density, depth, embedMedia }) => {
122
129
  const [open, setOpen] = React.useState(false);
123
130
  return /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
@@ -125,7 +132,7 @@ const NotApplicableRow = ({ entries, labels, density, depth, embedMedia }) => {
125
132
  e.stopPropagation();
126
133
  setOpen((v) => !v);
127
134
  }, children: [
128
- `${entries.length} ${entries.length === 1 ? "field" : "fields"} did not apply`,
135
+ notApplicableSummary(entries.map(([key]) => fieldLabel(key, labels).label)),
129
136
  open ? " \u2014 hide" : " \u2014 show"
130
137
  ] }),
131
138
  open && /* @__PURE__ */ jsx("dl", { className: "mt-1 grid grid-cols-[fit-content(40%)_minmax(0,1fr)] items-baseline gap-x-4 gap-y-1", children: entries.map(([key, val]) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
@@ -287,5 +294,6 @@ export {
287
294
  humanNameSiblingKey,
288
295
  isIdentifierKey,
289
296
  isIdentifierListKey,
290
- isNotApplicableValue
297
+ isNotApplicableValue,
298
+ notApplicableSummary
291
299
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/chat",
3
- "version": "0.3.19",
3
+ "version": "0.3.21",
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",
@@ -226,12 +226,12 @@
226
226
  "server-only": "^0.0.1",
227
227
  "uuid": "^14.0.2",
228
228
  "zod": "^4.6.5",
229
+ "@ai-matrx/agents": "latest",
229
230
  "@ai-matrx/alchemy": "latest",
230
- "@ai-matrx/associations": "latest",
231
231
  "@ai-matrx/browser-audio": "latest",
232
- "@ai-matrx/content-ir-react": "latest",
233
- "@ai-matrx/canvas": "latest",
232
+ "@ai-matrx/associations": "latest",
234
233
  "@ai-matrx/content-ir": "latest",
234
+ "@ai-matrx/canvas": "latest",
235
235
  "@ai-matrx/data": "latest",
236
236
  "@ai-matrx/design-system": "latest",
237
237
  "@ai-matrx/diff": "latest",
@@ -239,11 +239,11 @@
239
239
  "@ai-matrx/kit": "latest",
240
240
  "@ai-matrx/media": "latest",
241
241
  "@ai-matrx/print": "latest",
242
+ "@ai-matrx/content-ir-react": "latest",
242
243
  "@ai-matrx/realtime": "latest",
243
- "@ai-matrx/records": "latest",
244
244
  "@ai-matrx/records-ui": "latest",
245
245
  "@ai-matrx/tap-target": "latest",
246
- "@ai-matrx/agents": "latest"
246
+ "@ai-matrx/records": "latest"
247
247
  },
248
248
  "devDependencies": {
249
249
  "@arethetypeswrong/cli": "^0.18.5",