@nxisai/agent-harness 0.8.0 → 0.10.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.
- package/dist/a2ui/cards.d.ts +3 -0
- package/dist/a2ui/cards.d.ts.map +1 -1
- package/dist/a2ui/cards.js +11 -2
- package/dist/a2ui/cards.js.map +1 -1
- package/dist/harness/cta.d.ts +27 -0
- package/dist/harness/cta.d.ts.map +1 -0
- package/dist/harness/cta.js +43 -0
- package/dist/harness/cta.js.map +1 -0
- package/dist/harness/harness.d.ts +17 -0
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +40 -0
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/router/coordinator.d.ts +6 -0
- package/dist/harness/router/coordinator.d.ts.map +1 -1
- package/dist/harness/router/coordinator.js +14 -0
- package/dist/harness/router/coordinator.js.map +1 -1
- package/dist/harness/tools/action-tools.d.ts +28 -2
- package/dist/harness/tools/action-tools.d.ts.map +1 -1
- package/dist/harness/tools/action-tools.js +48 -2
- package/dist/harness/tools/action-tools.js.map +1 -1
- package/dist/harness/tools/bridge.d.ts.map +1 -1
- package/dist/harness/tools/bridge.js +2 -1
- package/dist/harness/tools/bridge.js.map +1 -1
- package/dist/harness/tools/inbound.d.ts +92 -0
- package/dist/harness/tools/inbound.d.ts.map +1 -0
- package/dist/harness/tools/inbound.js +170 -0
- package/dist/harness/tools/inbound.js.map +1 -0
- package/dist/harness/tools/report.d.ts +55 -0
- package/dist/harness/tools/report.d.ts.map +1 -0
- package/dist/harness/tools/report.js +75 -0
- package/dist/harness/tools/report.js.map +1 -0
- package/dist/harness/tools/source.d.ts.map +1 -1
- package/dist/harness/tools/source.js +17 -1
- package/dist/harness/tools/source.js.map +1 -1
- package/dist/harness/tools/webmcp.d.ts.map +1 -1
- package/dist/harness/tools/webmcp.js +5 -1
- package/dist/harness/tools/webmcp.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/ui/agent-fullscreen.d.ts.map +1 -1
- package/dist/ui/agent-fullscreen.js +78 -12
- package/dist/ui/agent-fullscreen.js.map +1 -1
- package/package.json +1 -1
- package/src/a2ui/cards.ts +14 -2
- package/src/harness/cta.ts +54 -0
- package/src/harness/harness.ts +42 -0
- package/src/harness/router/coordinator.ts +14 -0
- package/src/harness/tools/action-tools.ts +51 -2
- package/src/harness/tools/bridge.ts +2 -1
- package/src/harness/tools/inbound.ts +204 -0
- package/src/harness/tools/report.ts +119 -0
- package/src/harness/tools/source.ts +18 -1
- package/src/harness/tools/webmcp.ts +5 -1
- package/src/index.ts +14 -0
- package/src/ui/agent-fullscreen.ts +72 -5
package/src/index.ts
CHANGED
|
@@ -50,6 +50,9 @@ export type { Slot, Resolution, ResolveSources } from './harness/slots.js';
|
|
|
50
50
|
|
|
51
51
|
export { buildPlan } from './harness/plan.js';
|
|
52
52
|
|
|
53
|
+
export { actionCta } from './harness/cta.js';
|
|
54
|
+
export type { CardAction } from './harness/cta.js';
|
|
55
|
+
|
|
53
56
|
export { AgentCoordinator } from './harness/router/coordinator.js';
|
|
54
57
|
export type { CoordinatorOptions, RoutedTurn, RouteSource } from './harness/router/coordinator.js';
|
|
55
58
|
export { matchRoute } from './harness/router/rules.js';
|
|
@@ -134,6 +137,16 @@ export {
|
|
|
134
137
|
export type { AgentStep, AgentStepId, AgentStepStatus } from './harness/steps.js';
|
|
135
138
|
export { resumePendingAction } from './harness/tools/resume.js';
|
|
136
139
|
export type { ResumeOutcome, ResumeOptions } from './harness/tools/resume.js';
|
|
140
|
+
// Arriving from the NxisAI app to use one of this page's forms.
|
|
141
|
+
export {
|
|
142
|
+
HANDOFF_PARAM,
|
|
143
|
+
INTENT_PARAM,
|
|
144
|
+
readInboundHandoff,
|
|
145
|
+
takeInboundHandoff,
|
|
146
|
+
handoffUrl,
|
|
147
|
+
findHandoffForm,
|
|
148
|
+
} from './harness/tools/inbound.js';
|
|
149
|
+
export type { InboundHandoff, ArrivalOutcome, ArrivalOptions } from './harness/tools/inbound.js';
|
|
137
150
|
export {
|
|
138
151
|
loadSitePages,
|
|
139
152
|
parsePage,
|
|
@@ -145,6 +158,7 @@ export {
|
|
|
145
158
|
export type { SitePage, SitePagesOptions } from './harness/tools/site-pages.js';
|
|
146
159
|
export { buildOpenPageTool, registerOpenPageTool, resolveDestination, visitFor, OPEN_PAGE_TOOL } from './harness/tools/open-page.js';
|
|
147
160
|
export { isRateLimited, retryAfterMs, withRetry } from './harness/tools/retry.js';
|
|
161
|
+
export { setSiteActionReporter, TRACK_TOOL, type PageFormRun, type SiteActionReport, type SiteActionReporter } from './harness/tools/report.js';
|
|
148
162
|
export type { RetryOptions } from './harness/tools/retry.js';
|
|
149
163
|
export {
|
|
150
164
|
applyTrails,
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { mountSurface, type MountedSurface } from '../a2ui/host.js';
|
|
24
24
|
import { emitCardsSurface, type ResultCardData } from '../a2ui/cards.js';
|
|
25
|
+
import { actionCta, type CardAction } from '../harness/cta.js';
|
|
25
26
|
import { emitToolForm } from '../a2ui/form.js';
|
|
26
27
|
import { A2UI_THEME_CSS } from '../a2ui/theme.js';
|
|
27
28
|
// Compiled from agent.tw.css at build time (scripts/css-to-ts.mjs). It is a
|
|
@@ -46,6 +47,7 @@ import {
|
|
|
46
47
|
formsFor,
|
|
47
48
|
handoffFor,
|
|
48
49
|
isSiteTool,
|
|
50
|
+
siteFormFor,
|
|
49
51
|
withoutSiteSuffix,
|
|
50
52
|
HANDOFF_RESULT,
|
|
51
53
|
} from '../harness/tools/action-tools.js';
|
|
@@ -64,6 +66,7 @@ import { resultsAnswer, RESULTS_ANSWER_CSS, type AnswerCard } from './result-pic
|
|
|
64
66
|
import { extractRecords, openStatus, recordFacts } from '../harness/records.js';
|
|
65
67
|
import type { ResultPick } from '../harness/recommend.js';
|
|
66
68
|
import { resumePendingAction } from '../harness/tools/resume.js';
|
|
69
|
+
import { findHandoffForm, startFormMessage, startMessage, takeInboundHandoff } from '../harness/tools/inbound.js';
|
|
67
70
|
import { savePendingVisit, setConversationSource, takePendingVisit } from '../harness/tools/pending-action.js';
|
|
68
71
|
import { pageForEntity, type SitePage } from '../harness/tools/site-pages.js';
|
|
69
72
|
import { OPEN_PAGE_TOOL } from '../harness/tools/open-page.js';
|
|
@@ -341,6 +344,8 @@ interface ResultCard {
|
|
|
341
344
|
phone?: string;
|
|
342
345
|
/** What the record states, short, for comparing it. */
|
|
343
346
|
facts?: string[];
|
|
347
|
+
/** The action the record itself offers (its potentialAction), as a button. */
|
|
348
|
+
action?: CardAction;
|
|
344
349
|
}
|
|
345
350
|
|
|
346
351
|
type ThreadItem =
|
|
@@ -682,6 +687,8 @@ class NxisAgentFullscreen extends HTMLElement {
|
|
|
682
687
|
// belongs to is the one open.
|
|
683
688
|
void this._resumePendingAction();
|
|
684
689
|
this._resumePendingVisit();
|
|
690
|
+
// Sent here from the NxisAI app to use one of this page's forms.
|
|
691
|
+
void this._arriveFromHandoff();
|
|
685
692
|
|
|
686
693
|
this._els.launcher.addEventListener('click', () => this._open());
|
|
687
694
|
this._els.handoffBack.addEventListener('click', () => void this._endHandoff());
|
|
@@ -1589,7 +1596,14 @@ class NxisAgentFullscreen extends HTMLElement {
|
|
|
1589
1596
|
}
|
|
1590
1597
|
|
|
1591
1598
|
// ── Turn loop ──────────────────────────────────────────────────────────────
|
|
1592
|
-
|
|
1599
|
+
/**
|
|
1600
|
+
* Send what is in the composer.
|
|
1601
|
+
*
|
|
1602
|
+
* @param call - A tool call already decided, run without asking the model
|
|
1603
|
+
* (a handoff names its form). Falls back to an ordinary turn when no agent
|
|
1604
|
+
* here holds the tool.
|
|
1605
|
+
*/
|
|
1606
|
+
private async _onSend(call?: { name: string; args: Record<string, unknown> }): Promise<void> {
|
|
1593
1607
|
const goal = this._els.input.value.trim();
|
|
1594
1608
|
if (!goal || this._turnActive) return;
|
|
1595
1609
|
this._els.input.value = '';
|
|
@@ -1626,7 +1640,9 @@ class NxisAgentFullscreen extends HTMLElement {
|
|
|
1626
1640
|
await this._connectTools();
|
|
1627
1641
|
this._syncProfileContext();
|
|
1628
1642
|
// One agent, or several with a router in front: the signatures match.
|
|
1629
|
-
|
|
1643
|
+
const engine = this._coordinator ?? this._harness;
|
|
1644
|
+
const begun = call ? await engine.begin(goal, call.name, call.args, conv!.messages) : null;
|
|
1645
|
+
if (!begun || begun.decision === 'unavailable') await engine.turn(goal, conv!.messages);
|
|
1630
1646
|
// A remote endpoint reports which model answered; the name beside the
|
|
1631
1647
|
// input follows it rather than keeping whatever was configured.
|
|
1632
1648
|
this._paintProvenance();
|
|
@@ -2673,6 +2689,7 @@ class NxisAgentFullscreen extends HTMLElement {
|
|
|
2673
2689
|
facts.since ? `In business since ${facts.since}` : '',
|
|
2674
2690
|
facts.reviews[0] ? `\u201c${facts.reviews[0].text}\u201d${facts.reviews[0].author ? ` \u2014 ${facts.reviews[0].author}` : ''}` : '',
|
|
2675
2691
|
].filter((l): l is string => !!l);
|
|
2692
|
+
const action = actionCta(entity, title);
|
|
2676
2693
|
return {
|
|
2677
2694
|
title,
|
|
2678
2695
|
type,
|
|
@@ -2686,6 +2703,7 @@ class NxisAgentFullscreen extends HTMLElement {
|
|
|
2686
2703
|
...(facts.reviewCount !== undefined ? { reviews: facts.reviewCount } : {}),
|
|
2687
2704
|
...(status ? { status: { open: status.open, label: status.label } } : {}),
|
|
2688
2705
|
...(facts.phone ? { phone: facts.phone } : {}),
|
|
2706
|
+
...(action ? { action } : {}),
|
|
2689
2707
|
facts: lines,
|
|
2690
2708
|
};
|
|
2691
2709
|
}
|
|
@@ -2714,7 +2732,9 @@ class NxisAgentFullscreen extends HTMLElement {
|
|
|
2714
2732
|
const things = item.cards.filter((c) => c.stars !== undefined || c.status || c.phone || c.page);
|
|
2715
2733
|
if (!item.pick && !things.length) return null;
|
|
2716
2734
|
const cards: AnswerCard[] = item.cards.map((c) => {
|
|
2717
|
-
|
|
2735
|
+
// The record's own action first: the same map and prompt as the Typed Retrieval skill and the NxisAI
|
|
2736
|
+
// app. A record that states none falls back to a form found on this page for it.
|
|
2737
|
+
const form = c.action ? undefined : formsFor(c.title)[0];
|
|
2718
2738
|
const quote = form && /quote/.test(form.noun);
|
|
2719
2739
|
return {
|
|
2720
2740
|
title: c.title,
|
|
@@ -2724,7 +2744,9 @@ class NxisAgentFullscreen extends HTMLElement {
|
|
|
2724
2744
|
phone: c.phone,
|
|
2725
2745
|
page: c.page,
|
|
2726
2746
|
facts: c.facts ?? [],
|
|
2727
|
-
...(
|
|
2747
|
+
...(c.action
|
|
2748
|
+
? { form: { label: c.action.label, message: c.action.message } }
|
|
2749
|
+
: form
|
|
2728
2750
|
? {
|
|
2729
2751
|
form: quote
|
|
2730
2752
|
? { label: 'Get a quote', message: `Get a quote from ${c.title}` }
|
|
@@ -2762,6 +2784,7 @@ class NxisAgentFullscreen extends HTMLElement {
|
|
|
2762
2784
|
...(c.emergency ? [{ label: '24/7' }] : []),
|
|
2763
2785
|
],
|
|
2764
2786
|
rows: c.rows.map((r) => ({ label: r.label, value: r.value })),
|
|
2787
|
+
actions: c.action ? [c.action] : [],
|
|
2765
2788
|
}));
|
|
2766
2789
|
}
|
|
2767
2790
|
|
|
@@ -2775,7 +2798,15 @@ class NxisAgentFullscreen extends HTMLElement {
|
|
|
2775
2798
|
}
|
|
2776
2799
|
try {
|
|
2777
2800
|
const messages = emitCardsSurface(item.note, this._toA2uiCards(item.cards));
|
|
2778
|
-
const mounted = mountSurface(wrap, messages)
|
|
2801
|
+
const mounted = mountSurface(wrap, messages, (event: any) => {
|
|
2802
|
+
const name = event?.event?.name ?? event?.name ?? '';
|
|
2803
|
+
const ctx = (event?.event?.context ?? event?.context ?? {}) as Record<string, unknown>;
|
|
2804
|
+
// A card's action button: send its message as the visitor, as the inline card does.
|
|
2805
|
+
if (name === 'nxis.ask' && typeof ctx.message === 'string' && !this._turnActive) {
|
|
2806
|
+
this._els.input.value = ctx.message;
|
|
2807
|
+
void this._onSend();
|
|
2808
|
+
}
|
|
2809
|
+
});
|
|
2779
2810
|
if (!mounted) throw new Error('no surface produced');
|
|
2780
2811
|
this._surfaces.push(mounted);
|
|
2781
2812
|
} catch (err) {
|
|
@@ -3503,6 +3534,42 @@ class NxisAgentFullscreen extends HTMLElement {
|
|
|
3503
3534
|
if (this.mode !== 'fullscreen') this._open();
|
|
3504
3535
|
}
|
|
3505
3536
|
|
|
3537
|
+
/**
|
|
3538
|
+
* Open on the form a link from the NxisAI app names.
|
|
3539
|
+
*
|
|
3540
|
+
* In a new conversation: the person arrives from another app, and nothing
|
|
3541
|
+
* on this site led here. The agent starts the form in its own words, so the
|
|
3542
|
+
* link only chooses which form; what the person wanted waits in the
|
|
3543
|
+
* composer for them to send, change or delete.
|
|
3544
|
+
*/
|
|
3545
|
+
private async _arriveFromHandoff(): Promise<void> {
|
|
3546
|
+
const handoff = takeInboundHandoff();
|
|
3547
|
+
if (!handoff) return;
|
|
3548
|
+
this._newConversation();
|
|
3549
|
+
this._open();
|
|
3550
|
+
this._setStatus('Opening the form…');
|
|
3551
|
+
// The site's own tools first: they reach a form from any page, from the
|
|
3552
|
+
// site's records, so they work where the page registers nothing itself.
|
|
3553
|
+
await this._connectTools();
|
|
3554
|
+
const site = siteFormFor(handoff.actionId);
|
|
3555
|
+
const start = site
|
|
3556
|
+
? startFormMessage(site.noun, site.choice)
|
|
3557
|
+
: await findHandoffForm(handoff).then((o) => (o.status === 'ready' ? startMessage(o.tool) : null));
|
|
3558
|
+
this._setStatus('');
|
|
3559
|
+
if (!start) {
|
|
3560
|
+
this._appendMsg('assistant', "I couldn't find that form on this page. Tell me what you need and I'll help from here.");
|
|
3561
|
+
} else if (!this._turnActive) {
|
|
3562
|
+
this._els.input.value = start;
|
|
3563
|
+
// The form is known, so it opens without the model choosing it: left to
|
|
3564
|
+
// choose, a model sometimes asks for the details in prose instead.
|
|
3565
|
+
await this._onSend(site ? { name: site.tool, args: site.args } : undefined);
|
|
3566
|
+
}
|
|
3567
|
+
if (handoff.intent && !this._els.input.value) {
|
|
3568
|
+
this._els.input.value = handoff.intent;
|
|
3569
|
+
this._autosize();
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
|
|
3506
3573
|
private async _resumePendingAction(): Promise<void> {
|
|
3507
3574
|
const outcome = await resumePendingAction({
|
|
3508
3575
|
onStart: (pending) => {
|