@nxisai/agent-harness 0.4.1 → 0.6.0

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 (137) hide show
  1. package/README.md +25 -2
  2. package/dist/handoff/a2a.d.ts +152 -0
  3. package/dist/handoff/a2a.d.ts.map +1 -0
  4. package/dist/handoff/a2a.js +227 -0
  5. package/dist/handoff/a2a.js.map +1 -0
  6. package/dist/handoff/controller.d.ts +181 -0
  7. package/dist/handoff/controller.d.ts.map +1 -0
  8. package/dist/handoff/controller.js +543 -0
  9. package/dist/handoff/controller.js.map +1 -0
  10. package/dist/handoff/envelope.d.ts +130 -0
  11. package/dist/handoff/envelope.d.ts.map +1 -0
  12. package/dist/handoff/envelope.js +178 -0
  13. package/dist/handoff/envelope.js.map +1 -0
  14. package/dist/handoff/index.d.ts +16 -0
  15. package/dist/handoff/index.d.ts.map +1 -0
  16. package/dist/handoff/index.js +12 -0
  17. package/dist/handoff/index.js.map +1 -0
  18. package/dist/handoff/tools.d.ts +36 -0
  19. package/dist/handoff/tools.d.ts.map +1 -0
  20. package/dist/handoff/tools.js +148 -0
  21. package/dist/handoff/tools.js.map +1 -0
  22. package/dist/harness/harness.d.ts +4 -2
  23. package/dist/harness/harness.d.ts.map +1 -1
  24. package/dist/harness/harness.js +61 -16
  25. package/dist/harness/harness.js.map +1 -1
  26. package/dist/harness/router/coordinator.d.ts +13 -0
  27. package/dist/harness/router/coordinator.d.ts.map +1 -1
  28. package/dist/harness/router/coordinator.js +7 -0
  29. package/dist/harness/router/coordinator.js.map +1 -1
  30. package/dist/harness/tools/action-tools.d.ts.map +1 -1
  31. package/dist/harness/tools/action-tools.js +7 -5
  32. package/dist/harness/tools/action-tools.js.map +1 -1
  33. package/dist/harness/tools/bridge.d.ts.map +1 -1
  34. package/dist/harness/tools/bridge.js +33 -12
  35. package/dist/harness/tools/bridge.js.map +1 -1
  36. package/dist/harness/tools/discovery.d.ts +12 -1
  37. package/dist/harness/tools/discovery.d.ts.map +1 -1
  38. package/dist/harness/tools/discovery.js +19 -1
  39. package/dist/harness/tools/discovery.js.map +1 -1
  40. package/dist/harness/tools/local-context.d.ts +49 -9
  41. package/dist/harness/tools/local-context.d.ts.map +1 -1
  42. package/dist/harness/tools/local-context.js +377 -22
  43. package/dist/harness/tools/local-context.js.map +1 -1
  44. package/dist/harness/tools/open-page.d.ts +16 -0
  45. package/dist/harness/tools/open-page.d.ts.map +1 -1
  46. package/dist/harness/tools/open-page.js +45 -9
  47. package/dist/harness/tools/open-page.js.map +1 -1
  48. package/dist/harness/tools/retry.d.ts +53 -0
  49. package/dist/harness/tools/retry.d.ts.map +1 -0
  50. package/dist/harness/tools/retry.js +96 -0
  51. package/dist/harness/tools/retry.js.map +1 -0
  52. package/dist/harness/tools/schema.d.ts +18 -0
  53. package/dist/harness/tools/schema.d.ts.map +1 -1
  54. package/dist/harness/tools/schema.js +38 -0
  55. package/dist/harness/tools/schema.js.map +1 -1
  56. package/dist/harness/tools/site-actions.d.ts.map +1 -1
  57. package/dist/harness/tools/site-actions.js +3 -2
  58. package/dist/harness/tools/site-actions.js.map +1 -1
  59. package/dist/harness/tools/site-nav.d.ts +168 -0
  60. package/dist/harness/tools/site-nav.d.ts.map +1 -0
  61. package/dist/harness/tools/site-nav.js +316 -0
  62. package/dist/harness/tools/site-nav.js.map +1 -0
  63. package/dist/harness/tools/site-pages.d.ts +4 -0
  64. package/dist/harness/tools/site-pages.d.ts.map +1 -1
  65. package/dist/harness/tools/site-pages.js +19 -3
  66. package/dist/harness/tools/site-pages.js.map +1 -1
  67. package/dist/harness/tools/source.d.ts +9 -0
  68. package/dist/harness/tools/source.d.ts.map +1 -1
  69. package/dist/harness/tools/source.js +54 -14
  70. package/dist/harness/tools/source.js.map +1 -1
  71. package/dist/harness/tools/webmcp.d.ts +39 -3
  72. package/dist/harness/tools/webmcp.d.ts.map +1 -1
  73. package/dist/harness/tools/webmcp.js +93 -14
  74. package/dist/harness/tools/webmcp.js.map +1 -1
  75. package/dist/index.d.ts +6 -1
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +5 -1
  78. package/dist/index.js.map +1 -1
  79. package/dist/types.d.ts +2 -0
  80. package/dist/types.d.ts.map +1 -1
  81. package/dist/ui/agent-fullscreen.d.ts.map +1 -1
  82. package/dist/ui/agent-fullscreen.js +276 -2
  83. package/dist/ui/agent-fullscreen.js.map +1 -1
  84. package/dist/ui/tailwind.generated.d.ts +1 -1
  85. package/dist/ui/tailwind.generated.d.ts.map +1 -1
  86. package/dist/ui/tailwind.generated.js +1 -1
  87. package/dist/ui/tailwind.generated.js.map +1 -1
  88. package/package.json +2 -1
  89. package/src/a2ui/cards.ts +177 -0
  90. package/src/a2ui/emit.ts +147 -0
  91. package/src/a2ui/form.ts +117 -0
  92. package/src/a2ui/host.ts +73 -0
  93. package/src/a2ui/index.ts +22 -0
  94. package/src/a2ui/stepper.ts +211 -0
  95. package/src/a2ui/theme.ts +134 -0
  96. package/src/a2ui/to-v09.ts +118 -0
  97. package/src/engine.ts +701 -0
  98. package/src/handoff/a2a.ts +326 -0
  99. package/src/handoff/controller.ts +661 -0
  100. package/src/handoff/envelope.ts +216 -0
  101. package/src/handoff/index.ts +53 -0
  102. package/src/handoff/tools.ts +178 -0
  103. package/src/harness/harness.ts +981 -0
  104. package/src/harness/plan.ts +91 -0
  105. package/src/harness/policy.ts +59 -0
  106. package/src/harness/renderer.ts +208 -0
  107. package/src/harness/router/coordinator.ts +241 -0
  108. package/src/harness/router/rules.ts +65 -0
  109. package/src/harness/slots.ts +117 -0
  110. package/src/harness/steps.ts +156 -0
  111. package/src/harness/tools/action-tools.ts +870 -0
  112. package/src/harness/tools/bridge.ts +393 -0
  113. package/src/harness/tools/client.ts +26 -0
  114. package/src/harness/tools/discovery.ts +73 -0
  115. package/src/harness/tools/local-context.ts +480 -0
  116. package/src/harness/tools/open-page.ts +273 -0
  117. package/src/harness/tools/pending-action.ts +232 -0
  118. package/src/harness/tools/resume.ts +134 -0
  119. package/src/harness/tools/retry.ts +117 -0
  120. package/src/harness/tools/schema.ts +245 -0
  121. package/src/harness/tools/site-actions.ts +356 -0
  122. package/src/harness/tools/site-nav.ts +432 -0
  123. package/src/harness/tools/site-pages.ts +288 -0
  124. package/src/harness/tools/source.ts +353 -0
  125. package/src/harness/tools/webmcp.ts +200 -0
  126. package/src/index.ts +196 -0
  127. package/src/providers/capability.ts +167 -0
  128. package/src/providers/proxy.ts +191 -0
  129. package/src/providers/types.ts +150 -0
  130. package/src/types.ts +258 -0
  131. package/src/ui/agent-fullscreen.ts +4262 -0
  132. package/src/ui/agent.tw.css +49 -0
  133. package/src/ui/config.ts +138 -0
  134. package/src/ui/index.ts +32 -0
  135. package/src/ui/journey.ts +57 -0
  136. package/src/ui/tailwind.generated.ts +2 -0
  137. package/src/webmcp-contract.ts +65 -0
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Merges every known source of a tool's arguments and reports what is missing.
3
+ *
4
+ * Sources merge by precedence, most authoritative last:
5
+ *
6
+ * ```
7
+ * schema defaults < page context < saved profile < model args
8
+ * ```
9
+ *
10
+ * Model arguments win because they encode what the user has just asked for, so
11
+ * an explicit value in the request overrides a stored preference.
12
+ *
13
+ * @module
14
+ */
15
+
16
+ import type { JsonSchema } from '../types.js';
17
+
18
+ /** One argument of a tool call, with its value and where that value came from. */
19
+ export interface Slot {
20
+ /** Property key in the flat, model-facing schema. */
21
+ name: string;
22
+ /** The property's JSON Schema, used for labels, types and enums. */
23
+ schema: JsonSchema;
24
+ /** Whether the tool's schema lists this property as required. */
25
+ required: boolean;
26
+ /** Which source supplied {@link Slot.value}; `'none'` when nothing did. */
27
+ source: 'model' | 'profile' | 'context' | 'default' | 'none';
28
+ /** The resolved value, or `undefined` when `source` is `'none'`. */
29
+ value: unknown;
30
+ }
31
+
32
+ /** The outcome of merging every source for one tool call. */
33
+ export interface Resolution {
34
+ /** Every known value, ready to submit when `missing` is empty. */
35
+ values: Record<string, unknown>;
36
+ /** Required slots nobody supplied: the values to ask for. */
37
+ missing: Slot[];
38
+ /**
39
+ * Optional slots left empty. Never asked about on their own; a write
40
+ * confirmation may still surface them as editable extras.
41
+ */
42
+ optional: Slot[];
43
+ /** Every slot in the schema, resolved or not. */
44
+ all: Slot[];
45
+ }
46
+
47
+ /** The candidate values to merge, in order of increasing precedence. */
48
+ export interface ResolveSources {
49
+ /** Arguments the model supplied in its tool call. */
50
+ modelArgs?: Record<string, unknown>;
51
+ /** Values saved from earlier turns, such as a name or an email address. */
52
+ profile?: Record<string, unknown>;
53
+ /** Values the host page knows, such as the current record. */
54
+ context?: Record<string, unknown>;
55
+ /** Values a human has typed into a live form. Ranks with `modelArgs`. */
56
+ entered?: Record<string, unknown>;
57
+ }
58
+
59
+ /** Present and meaningful: not null, not undefined, not whitespace. */
60
+ const isSet = (v: unknown): boolean =>
61
+ v !== undefined && v !== null && !(typeof v === 'string' && v.trim() === '');
62
+
63
+ /**
64
+ * Resolve every property in a schema against the supplied sources.
65
+ *
66
+ * @param schema - The tool's flat input schema.
67
+ * @param sources - Candidate values, merged by the precedence in the module
68
+ * description.
69
+ * @returns The merged values, plus the required and optional slots still empty.
70
+ */
71
+ export function resolveSlots(schema: JsonSchema, sources: ResolveSources): Resolution {
72
+ const props = schema.properties ?? {};
73
+ const required = new Set(schema.required ?? []);
74
+
75
+ const all: Slot[] = Object.entries(props).map(([name, spec]) => {
76
+ const propSchema = spec ?? {};
77
+
78
+ // Highest precedence first; the first hit wins and records its origin.
79
+ const candidates: [Slot['source'], unknown][] = [
80
+ ['model', sources.entered?.[name]],
81
+ ['model', sources.modelArgs?.[name]],
82
+ ['profile', sources.profile?.[name]],
83
+ ['context', sources.context?.[name]],
84
+ ['default', propSchema.default],
85
+ ];
86
+
87
+ for (const [source, value] of candidates) {
88
+ if (isSet(value)) {
89
+ return { name, schema: propSchema, required: required.has(name), source, value };
90
+ }
91
+ }
92
+ return { name, schema: propSchema, required: required.has(name), source: 'none', value: undefined };
93
+ });
94
+
95
+ const values: Record<string, unknown> = {};
96
+ for (const slot of all) if (isSet(slot.value)) values[slot.name] = slot.value;
97
+
98
+ return {
99
+ values,
100
+ missing: all.filter(s => s.required && s.source === 'none'),
101
+ optional: all.filter(s => !s.required && s.source === 'none'),
102
+ all,
103
+ };
104
+ }
105
+
106
+ /**
107
+ * The slots to display on a write confirmation: those with a value.
108
+ *
109
+ * Empty optional slots are omitted, since a confirmation shows what is about
110
+ * to be sent rather than what could have been.
111
+ *
112
+ * @param resolution - A resolution with no remaining gaps.
113
+ * @returns Every slot whose value came from a source.
114
+ */
115
+ export function summarySlots(resolution: Resolution): Slot[] {
116
+ return resolution.all.filter(s => s.source !== 'none');
117
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * What the agent is doing, as a short list a surface can show.
3
+ *
4
+ * A chat thread says what was said; it does not say that the page is about to
5
+ * change, or that a form is being filled right now. A copilot surface beside
6
+ * the page needs the second thing, and so does anyone watching a call travel
7
+ * to another page and finish there.
8
+ *
9
+ * The journey outlives the page. A call confirmed on one page is submitted on
10
+ * another, so the steps taken before the navigation are persisted with the
11
+ * call and restored on arrival — otherwise the surface that exists to explain
12
+ * the journey would show only its last step.
13
+ *
14
+ * @module
15
+ */
16
+
17
+ /**
18
+ * The steps a turn can take, in the order they happen.
19
+ *
20
+ * A closed set, so a surface can give each one its own words and ordering
21
+ * rather than printing whatever it is handed. Surfaces should still ignore an
22
+ * id they do not know: this list may grow.
23
+ */
24
+ export type AgentStepId =
25
+ /** A tool was chosen for the request. */
26
+ | 'choose'
27
+ /** Values are being collected from the user. */
28
+ | 'collect'
29
+ /** Everything is known; a human is being asked to press submit. */
30
+ | 'confirm'
31
+ /** Leaving for the page that can run the action. */
32
+ | 'navigate'
33
+ /** That page is open. */
34
+ | 'arrive'
35
+ /** The form is being filled and submitted. */
36
+ | 'fill'
37
+ /** The turn finished. */
38
+ | 'submit';
39
+
40
+ /** How far a step got. */
41
+ export type AgentStepStatus = 'active' | 'done' | 'failed';
42
+
43
+ /** One step of what the agent is doing. */
44
+ export interface AgentStep {
45
+ /** Which step this is. Recording the same id again updates it in place. */
46
+ id: AgentStepId;
47
+ /**
48
+ * Which journey the step belongs to — one per request that acts.
49
+ *
50
+ * A conversation holds many requests, and a surface that keeps what was
51
+ * done needs to know which of them a step belongs to. Filled in when the
52
+ * step is recorded; carried across a page change with the rest.
53
+ */
54
+ journey?: string;
55
+ /** What is happening, in the user's words: `Filling the quote form`. */
56
+ label: string;
57
+ /** How far it got. */
58
+ status: AgentStepStatus;
59
+ /** A quieter second line: `name, email, service`. */
60
+ detail?: string;
61
+ }
62
+
63
+ type Listener = (step: AgentStep, all: AgentStep[]) => void;
64
+
65
+ let journey: AgentStep[] = [];
66
+ let journeyId: string | null = null;
67
+ const listeners = new Set<Listener>();
68
+
69
+ const newJourneyId = (): string =>
70
+ 'j' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
71
+
72
+ /**
73
+ * Start a new journey, discarding the last one.
74
+ *
75
+ * Called when a turn begins, so a surface does not show the previous
76
+ * request's steps beside this one's.
77
+ *
78
+ * @returns The new journey's id, which every step recorded until the next
79
+ * call carries.
80
+ */
81
+ export function beginJourney(): string {
82
+ journey = [];
83
+ journeyId = newJourneyId();
84
+ notify(undefined);
85
+ return journeyId;
86
+ }
87
+
88
+ /** The journey steps are being recorded into, or null before the first. */
89
+ export function currentJourney(): string | null {
90
+ return journeyId;
91
+ }
92
+
93
+ /**
94
+ * Record a step, or update one already recorded.
95
+ *
96
+ * @param step - The step. An id already present is replaced where it stands,
97
+ * so `active` becoming `done` does not append a second line. The step is
98
+ * stamped with the current journey.
99
+ */
100
+ export function recordStep(step: AgentStep): void {
101
+ journeyId ??= newJourneyId();
102
+ const stamped = { ...step, journey: journeyId };
103
+ const at = journey.findIndex((s) => s.id === step.id);
104
+ if (at >= 0) journey[at] = { ...journey[at], ...stamped };
105
+ else journey.push(stamped);
106
+ notify(stamped);
107
+ }
108
+
109
+ /** The steps so far, oldest first. */
110
+ export function journeySteps(): AgentStep[] {
111
+ return journey.map((s) => ({ ...s }));
112
+ }
113
+
114
+ /**
115
+ * Replace the journey with one that was carried here.
116
+ *
117
+ * Used on arrival, where the steps that led to the page change happened
118
+ * before this page existed.
119
+ */
120
+ export function restoreJourney(steps: AgentStep[] | undefined): void {
121
+ journey = (steps ?? []).map((s) => ({ ...s }));
122
+ // The same journey, continued: steps recorded from here belong with the
123
+ // ones taken on the page before, not to a new request.
124
+ journeyId = journey.find((s) => s.journey)?.journey ?? newJourneyId();
125
+ journey = journey.map((s) => ({ ...s, journey: journeyId! }));
126
+ notify(undefined);
127
+ }
128
+
129
+ /**
130
+ * Watch the journey.
131
+ *
132
+ * A renderer receives steps through {@link HarnessRenderer.step} and needs
133
+ * none of this; a surface drawn outside the harness — a side panel the host
134
+ * owns — subscribes here instead.
135
+ *
136
+ * @param listener - Called with the step that changed, and the whole list.
137
+ * @returns Stop listening.
138
+ */
139
+ export function onJourneyChange(listener: Listener): () => void {
140
+ listeners.add(listener);
141
+ return () => listeners.delete(listener);
142
+ }
143
+
144
+ function notify(step: AgentStep | undefined): void {
145
+ const all = journeySteps();
146
+ // A restore or a reset has no single step to point at; the list is the news.
147
+ const changed = step ?? all[all.length - 1] ?? { id: 'choose' as const, label: '', status: 'done' as const };
148
+ for (const listener of listeners) {
149
+ try {
150
+ listener(changed, all);
151
+ } catch (err) {
152
+ // One surface throwing must not stop the next from being told.
153
+ console.warn('[nxis-agent] a step listener threw:', err);
154
+ }
155
+ }
156
+ }