@cynodia/axiom-agent-api 0.5.0-alpha.1 → 0.5.2-alpha.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.
package/README.md CHANGED
@@ -5,13 +5,12 @@ framework.
5
5
 
6
6
  **Status: experimental / alpha.** The API may change between alpha releases.
7
7
 
8
- The machine-facing interface: semantic queries over the graph, field-level dependency
9
- and mutation-impact analysis, and transactional graph transformations.
8
+ The machine-facing interface: semantic queries over the graph, field-level dependency and
9
+ mutation-impact analysis, presentation and UX queries, and transactional graph
10
+ transformations.
10
11
 
11
- Presentation and UX are queryable too — which action a view presents as primary, which
12
- controls are destructive, how a form is grouped, what happens on a compact display, which
13
- screens carry presentation warnings — and transformable: "make this form compact" is one
14
- semantic change, and an application-wide restyling is a single theme change.
12
+ Main exports: `AgentAPI`, `GraphQueries`, `PresentationQueries`, `Transaction`,
13
+ `ChangeSet`.
15
14
 
16
15
  ## Installation
17
16
 
@@ -25,6 +24,13 @@ Most applications should install the facade package instead, which re-exports th
25
24
  npm install @cynodia/axiom@alpha
26
25
  ```
27
26
 
27
+
28
+ ## Documentation
29
+
30
+ The canonical operational contract lives in the `docs/` directory of the
31
+ [`@cynodia/axiom`](https://www.npmjs.com/package/@cynodia/axiom) package, and in
32
+ [the repository](https://github.com/cynodia/axiom). Start with `docs/AGENT_REFERENCE.md`.
33
+
28
34
  ## License
29
35
 
30
36
  MIT
@@ -1,4 +1,4 @@
1
- import type { ActionDef, Density, DeviceClass, FormNode, NodeId, Presentation, PresentationRole, ResolvedPresentation, ResolvedResponsive, StateDef, Theme, UINode, UxRole, ValidationIssue, ViewNode } from '@cynodia/axiom-core';
1
+ import type { ActionDef, Density, DeviceClass, DiagnosticNode, FormNode, NodeId, Presentation, PresentationRole, ResolvedPresentation, ResolvedResponsive, StateDef, Theme, UINode, UxRole, ValidationIssue, ViewNode } from '@cynodia/axiom-core';
2
2
  import { GraphQueries } from './queries.js';
3
3
  /** One grouped part of a form, and what it contains. */
4
4
  export interface FormSectionSummary {
@@ -12,10 +12,18 @@ export interface FormSectionSummary {
12
12
  * The shape of a form as UX, rather than as a list of children: which parts are sections,
13
13
  * which controls are required, where the actions are and which one is primary.
14
14
  */
15
+ /**
16
+ * The **declared** structure of a form: what it contains, not what is on screen right now.
17
+ *
18
+ * It is read along the primary render path, so an alternative branch — an empty template,
19
+ * a conditional's false branch — is not described as part of the form's structure.
20
+ */
15
21
  export interface FormStructure {
16
22
  formId: NodeId;
17
23
  density: Density;
18
24
  submitActionId?: NodeId;
25
+ /** Set when the form uses a declared `ButtonNode` as its submit control. */
26
+ submitButtonId?: NodeId;
19
27
  sections: FormSectionSummary[];
20
28
  /** Inputs that belong to no section. */
21
29
  ungroupedInputIds: NodeId[];
@@ -63,6 +71,16 @@ export declare class PresentationQueries extends GraphQueries {
63
71
  getFormsWithoutPrimaryAction(): FormNode[];
64
72
  /** Nodes carrying renderer-specific presentation that cannot be analyzed. */
65
73
  getOpaquePresentationNodes(): UINode[];
74
+ /** UI nodes that present failures from this action. */
75
+ getDiagnosticPresentations(actionId: NodeId): DiagnosticNode[];
76
+ /**
77
+ * Actions that can refuse but whose refusal no UI node presents.
78
+ *
79
+ * An action counts as able to refuse if it declares a guard, a precondition or a
80
+ * postcondition. Only actions a control actually invokes are reported, since an action
81
+ * nothing invokes has no refusal to explain.
82
+ */
83
+ getActionsWithoutDiagnosticPresentation(): ActionDef[];
66
84
  /** States marked as ephemeral presentation state rather than domain facts. */
67
85
  getEphemeralStates(): StateDef[];
68
86
  /**
@@ -74,7 +92,14 @@ export declare class PresentationQueries extends GraphQueries {
74
92
  getFormStructure(formId: NodeId): FormStructure;
75
93
  protected presentationMap(): Record<NodeId, ResolvedPresentation>;
76
94
  protected uiNodes(): UINode[];
77
- protected descendants(id: NodeId): UINode[];
95
+ /**
96
+ * UI nodes beneath this one. `primaryPathOnly` restricts the walk to the arrangement that
97
+ * appears when every collection has members and every condition holds, which is what
98
+ * "structure that is on screen together" means.
99
+ */
100
+ protected descendants(id: NodeId, options?: {
101
+ primaryPathOnly?: boolean;
102
+ }): UINode[];
78
103
  protected descendantIds(id: NodeId): NodeId[];
79
104
  private actionsWithUxRole;
80
105
  }
@@ -1,4 +1,4 @@
1
- import { isUINode, resolvePresentationMap, uiChildIds, validateGraph, } from '@cynodia/axiom-core';
1
+ import { isUINode, primaryChildIds, resolvePresentationMap, uiChildIds, validateGraph, } from '@cynodia/axiom-core';
2
2
  import { GraphQueries } from './queries.js';
3
3
  /** Diagnostic codes produced by the presentation layer. */
4
4
  const PRESENTATION_CODES = [
@@ -97,6 +97,40 @@ export class PresentationQueries extends GraphQueries {
97
97
  const resolved = this.presentationMap();
98
98
  return this.uiNodes().filter((node) => resolved[node.id]?.opaque === true);
99
99
  }
100
+ /** UI nodes that present failures from this action. */
101
+ getDiagnosticPresentations(actionId) {
102
+ return this.uiNodes().filter((node) => node.kind === 'diagnostic' && node.actionId === actionId);
103
+ }
104
+ /**
105
+ * Actions that can refuse but whose refusal no UI node presents.
106
+ *
107
+ * An action counts as able to refuse if it declares a guard, a precondition or a
108
+ * postcondition. Only actions a control actually invokes are reported, since an action
109
+ * nothing invokes has no refusal to explain.
110
+ */
111
+ getActionsWithoutDiagnosticPresentation() {
112
+ const invoked = new Set();
113
+ for (const node of this.uiNodes()) {
114
+ if (node.kind === 'button') {
115
+ invoked.add(node.actionId);
116
+ }
117
+ if (node.kind === 'form' && node.submitActionId) {
118
+ invoked.add(node.submitActionId);
119
+ }
120
+ }
121
+ const presented = new Set(this.uiNodes()
122
+ .filter((node) => node.kind === 'diagnostic')
123
+ .map((node) => node.actionId));
124
+ return this.graph.getNodesByKind('action').filter((action) => {
125
+ if (!invoked.has(action.id) || presented.has(action.id)) {
126
+ return false;
127
+ }
128
+ const canRefuse = (action.guards ?? []).length > 0 ||
129
+ (action.preconditions ?? []).length > 0 ||
130
+ (action.postconditions ?? []).length > 0;
131
+ return canRefuse;
132
+ });
133
+ }
100
134
  /** States marked as ephemeral presentation state rather than domain facts. */
101
135
  getEphemeralStates() {
102
136
  return this.graph.getNodesByKind('state').filter((state) => state.ephemeral === true);
@@ -128,13 +162,20 @@ export class PresentationQueries extends GraphQueries {
128
162
  const destructiveActionIds = new Set();
129
163
  const requiredInputIds = [];
130
164
  const allInputIds = [];
131
- if (form.submitActionId) {
132
- primaryActionIds.add(form.submitActionId);
165
+ const submitButton = form.submitButtonId
166
+ ? this.graph.getNode(form.submitButtonId)
167
+ : undefined;
168
+ const submitActionId = form.submitActionId ??
169
+ (submitButton && isUINode(submitButton) && submitButton.kind === 'button'
170
+ ? submitButton.actionId
171
+ : undefined);
172
+ if (submitActionId) {
173
+ primaryActionIds.add(submitActionId);
133
174
  }
134
- for (const node of this.descendants(formId)) {
175
+ for (const node of this.descendants(formId, { primaryPathOnly: true })) {
135
176
  const view = resolved[node.id];
136
177
  if (view?.uxRole === 'form-section') {
137
- const inner = this.descendants(node.id);
178
+ const inner = this.descendants(node.id, { primaryPathOnly: true });
138
179
  const inputIds = inner.filter((child) => child.kind === 'input').map((child) => child.id);
139
180
  inputIds.forEach((id) => sectionInputs.add(id));
140
181
  sections.push({
@@ -170,7 +211,8 @@ export class PresentationQueries extends GraphQueries {
170
211
  return {
171
212
  formId,
172
213
  density: resolved[formId]?.density ?? 'comfortable',
173
- ...(form.submitActionId ? { submitActionId: form.submitActionId } : {}),
214
+ ...(submitActionId ? { submitActionId } : {}),
215
+ ...(form.submitButtonId ? { submitButtonId: form.submitButtonId } : {}),
174
216
  sections,
175
217
  ungroupedInputIds: allInputIds.filter((id) => !sectionInputs.has(id)),
176
218
  actionGroupIds,
@@ -186,7 +228,13 @@ export class PresentationQueries extends GraphQueries {
186
228
  uiNodes() {
187
229
  return this.graph.listNodes().filter((node) => isUINode(node));
188
230
  }
189
- descendants(id) {
231
+ /**
232
+ * UI nodes beneath this one. `primaryPathOnly` restricts the walk to the arrangement that
233
+ * appears when every collection has members and every condition holds, which is what
234
+ * "structure that is on screen together" means.
235
+ */
236
+ descendants(id, options = {}) {
237
+ const children = options.primaryPathOnly ? primaryChildIds : uiChildIds;
190
238
  const found = [];
191
239
  const seen = new Set([id]);
192
240
  const visit = (current) => {
@@ -194,7 +242,7 @@ export class PresentationQueries extends GraphQueries {
194
242
  if (!node || !isUINode(node)) {
195
243
  return;
196
244
  }
197
- for (const childId of uiChildIds(node)) {
245
+ for (const childId of children(node)) {
198
246
  if (seen.has(childId)) {
199
247
  continue;
200
248
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cynodia/axiom-agent-api",
3
- "version": "0.5.0-alpha.1",
3
+ "version": "0.5.2-alpha.1",
4
4
  "description": "Semantic queries and transactional graph transformations for AI agents.",
5
5
  "license": "MIT",
6
6
  "author": "AskTech AS",
@@ -31,7 +31,7 @@
31
31
  }
32
32
  },
33
33
  "dependencies": {
34
- "@cynodia/axiom-core": "0.5.0-alpha.1"
34
+ "@cynodia/axiom-core": "0.5.2-alpha.1"
35
35
  },
36
36
  "scripts": {
37
37
  "build": "tsc -b tsconfig.json tsconfig.test.json",