@ai-matrx/chat 0.3.20 → 0.3.23

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,25 @@
1
1
  # @ai-matrx/chat
2
2
 
3
+ ## 0.3.23 — 2026-10-07
4
+ ### The working document reaches the agent again
5
+
6
+ - The page rule (2026-10-01) withheld every value the chat page declares from the chat's own conversation. The chat manifest declares a lean ref named `working_document`, so the conversation's real, agent-editable working document was sent as `context_withheld: ["working_document"]` on every main-chat turn — the agent answered "I don't see a document". `working_document`, `user_scratchpad` and `user_scratchpad_<id8>` are now never the page's (`request-context.ts` `neverThePage`), matching aidream `page_withholds` and `@ai-matrx/agents` `pageContextWithholds` (parity corpus cases added).
7
+ - Consumer action: none.
8
+
9
+ ## 0.3.22 — 2026-10-07
10
+
11
+ - `useDomCapturePrint`'s PDF goes through Alchemy's `/operate/capture` `documentToPdf` + kit `downloadFile` (needs alchemy 0.20.0), not `@ai-matrx/print/pdf` directly (AP-2 fifth check). Same options, same file name.
12
+
13
+ **Consumer action:** none.
14
+
15
+ ## 0.3.21 — 2026-10-07
16
+ ### The editable definition leaves chat (AGENT-CORE-PLAN B2, builder tier)
17
+
18
+ - 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).
19
+ - Added: `markAgentRecordClean(record)` (slice) — the one "clean baseline" helper, shared by `upsertAgent` and the host's builder.
20
+ - Guard G2 (`check:no-builder-tier`) is now a hard ban: 0 builder-tier references in chat source, no baseline.
21
+ - 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.
22
+
3
23
  ## 0.3.20 — 2026-10-07
4
24
  ### Working-document realtime channel renamed
5
25
 
@@ -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,
@@ -41,6 +41,10 @@ import { selectSavedContextRuleRows } from "./context-rules.thunks.js";
41
41
  import { deliveredFieldsFor, toContextReceipt } from "./receipt-check.js";
42
42
  import { resolveClientSurface } from "../utils/build-tool-injection.js";
43
43
  import { surfaceWritesNoteSource } from "../utils/surface-writes-note.js";
44
+ import { docKindForContextKey } from "../../../utils/workingDocumentContext.js";
45
+ function neverThePage(key) {
46
+ return PERSON_CONTEXT_VALUES.has(key) || docKindForContextKey(key) !== null;
47
+ }
44
48
  function pageContextFor(state, conversationId) {
45
49
  const owner = pageOwningConversation(conversationId);
46
50
  if (owner) {
@@ -49,7 +53,7 @@ function pageContextFor(state, conversationId) {
49
53
  return {
50
54
  mode: "own",
51
55
  withheld: listed.filter(
52
- (key) => !PERSON_CONTEXT_VALUES.has(key) && !(companionOpen && key === SURFACE_CHAIN_KEY)
56
+ (key) => !neverThePage(key) && !(companionOpen && key === SURFACE_CHAIN_KEY)
53
57
  )
54
58
  };
55
59
  }
@@ -59,7 +63,7 @@ function pageContextFor(state, conversationId) {
59
63
  return null;
60
64
  }
61
65
  function pageWithholds(page, key, surfaceKey) {
62
- if (!page || PERSON_CONTEXT_VALUES.has(key)) return false;
66
+ if (!page || neverThePage(key)) return false;
63
67
  if (page.withheld.includes(key)) return true;
64
68
  return Boolean(surfaceKey && surfaceKey !== DEFAULT_SURFACE_KEY);
65
69
  }
@@ -18,16 +18,20 @@ function useDomCapturePrint() {
18
18
  try {
19
19
  const { renderAllDiagrams } = await import("../../host/markdown-slots.js");
20
20
  releaseRenderAll = (await renderAllDiagrams()).release;
21
- const { captureToPDF } = await import("@ai-matrx/print/pdf");
22
- await captureToPDF(el, {
23
- filename: "ai-response",
21
+ const [{ documentToPdf }, { downloadFile }] = await Promise.all([
22
+ import("@ai-matrx/alchemy/operate/capture"),
23
+ import("@ai-matrx/kit/download")
24
+ ]);
25
+ const { filename = "ai-response", ...captureOptions } = options;
26
+ const pdf = await documentToPdf(el, {
24
27
  scale: 2,
25
- ...options,
28
+ ...captureOptions,
26
29
  onProgress: (page, total) => {
27
30
  setProgress(Math.round(page / Math.max(total, 1) * 100));
28
31
  options.onProgress?.(page, total);
29
32
  }
30
33
  });
34
+ downloadFile(`${filename}.pdf`, pdf, "application/pdf");
31
35
  } catch (err) {
32
36
  const msg = err instanceof Error ? err.message : "PDF export failed";
33
37
  setError(msg);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/chat",
3
- "version": "0.3.20",
3
+ "version": "0.3.23",
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,24 +226,24 @@
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
+ "@ai-matrx/alchemy": "latest",
230
230
  "@ai-matrx/associations": "latest",
231
+ "@ai-matrx/agents": "latest",
231
232
  "@ai-matrx/canvas": "latest",
232
- "@ai-matrx/browser-audio": "latest",
233
- "@ai-matrx/alchemy": "latest",
234
233
  "@ai-matrx/content-ir": "latest",
235
- "@ai-matrx/content-ir-react": "latest",
236
- "@ai-matrx/data": "latest",
234
+ "@ai-matrx/browser-audio": "latest",
237
235
  "@ai-matrx/design-system": "latest",
238
236
  "@ai-matrx/diff": "latest",
237
+ "@ai-matrx/data": "latest",
239
238
  "@ai-matrx/icons": "latest",
239
+ "@ai-matrx/kit": "latest",
240
240
  "@ai-matrx/media": "latest",
241
241
  "@ai-matrx/print": "latest",
242
242
  "@ai-matrx/realtime": "latest",
243
- "@ai-matrx/kit": "latest",
244
- "@ai-matrx/tap-target": "latest",
245
243
  "@ai-matrx/records": "latest",
246
- "@ai-matrx/records-ui": "latest"
244
+ "@ai-matrx/records-ui": "latest",
245
+ "@ai-matrx/tap-target": "latest",
246
+ "@ai-matrx/content-ir-react": "latest"
247
247
  },
248
248
  "devDependencies": {
249
249
  "@arethetypeswrong/cli": "^0.18.5",