@burdenoff/fe-libs 2026.1024.4 → 2026.1027.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/dist/credentials/SchemaForm.js +2 -0
- package/dist/shared/agent-tools/AgentToolsProvider.d.ts +33 -0
- package/dist/shared/agent-tools/AgentToolsProvider.js +48 -0
- package/dist/shared/agent-tools/adapters/assistant.d.ts +85 -0
- package/dist/shared/agent-tools/adapters/assistant.js +110 -0
- package/dist/shared/agent-tools/adapters/webmcp.d.ts +69 -0
- package/dist/shared/agent-tools/adapters/webmcp.js +81 -0
- package/dist/shared/agent-tools/confirm/AgentConfirmHost.d.ts +12 -0
- package/dist/shared/agent-tools/confirm/AgentConfirmHost.js +34 -0
- package/dist/shared/agent-tools/confirm/confirmStore.d.ts +37 -0
- package/dist/shared/agent-tools/confirm/confirmStore.js +58 -0
- package/dist/shared/agent-tools/defineAgentTool.d.ts +13 -0
- package/dist/shared/agent-tools/defineAgentTool.js +27 -0
- package/dist/shared/agent-tools/errors.d.ts +33 -0
- package/dist/shared/agent-tools/errors.js +105 -0
- package/dist/shared/agent-tools/index.d.ts +29 -0
- package/dist/shared/agent-tools/limits.d.ts +20 -0
- package/dist/shared/agent-tools/limits.js +4 -0
- package/dist/shared/agent-tools/log.d.ts +15 -0
- package/dist/shared/agent-tools/log.js +11 -0
- package/dist/shared/agent-tools/outputBudget.d.ts +21 -0
- package/dist/shared/agent-tools/outputBudget.js +51 -0
- package/dist/shared/agent-tools/registry.d.ts +13 -0
- package/dist/shared/agent-tools/registry.js +53 -0
- package/dist/shared/agent-tools/runAgentTool.d.ts +21 -0
- package/dist/shared/agent-tools/runAgentTool.js +57 -0
- package/dist/shared/agent-tools/shell/shellTools.d.ts +44 -0
- package/dist/shared/agent-tools/shell/shellTools.js +145 -0
- package/dist/shared/agent-tools/testSupport/installDom.d.ts +6 -0
- package/dist/shared/agent-tools/types.d.ts +106 -0
- package/dist/shared/agent-tools/ui/dom.d.ts +43 -0
- package/dist/shared/agent-tools/ui/dom.js +178 -0
- package/dist/shared/agent-tools/ui/refs.d.ts +8 -0
- package/dist/shared/agent-tools/ui/refs.js +20 -0
- package/dist/shared/agent-tools/ui/uiTools.d.ts +40 -0
- package/dist/shared/agent-tools/ui/uiTools.js +277 -0
- package/dist/shared/agent-tools/useAgentTools.d.ts +14 -0
- package/dist/shared/agent-tools/useAgentTools.js +14 -0
- package/dist/shared/assistant/api.d.ts +18 -0
- package/dist/shared/assistant/api.js +145 -131
- package/dist/shared/assistant/avatar/live/LiveAvatarStage.js +2 -3
- package/dist/shared/assistant/avatar/live/LiveAvatarTabView.js +2 -3
- package/dist/shared/assistant/avatar/live/avatarPorts.d.ts +9 -0
- package/dist/shared/assistant/avatar/live/avatarTransport.d.ts +25 -3
- package/dist/shared/assistant/avatar/live/avatarTransport.js +29 -2
- package/dist/shared/assistant/avatar/live/createLiveAvatarSeam.d.ts +22 -4
- package/dist/shared/assistant/avatar/live/createLiveAvatarSeam.js +113 -81
- package/dist/shared/assistant/avatar/live/liveAvatarController.d.ts +15 -1
- package/dist/shared/assistant/avatar/live/liveAvatarController.js +204 -186
- package/dist/shared/assistant/avatar/live/liveKitAvatarRoom.js +6 -1
- package/dist/shared/assistant/ui/AssistantChatArea.js +1158 -1148
- package/dist/shared/assistant/ui/assistantLiveAvatarSeam.d.ts +1 -1
- package/dist/shared/assistant/writeProposals.d.ts +6 -0
- package/dist/shared/providers/shell/ActiveContextProvider.d.ts +5 -0
- package/dist/shared/providers/shell/ActiveContextProvider.js +4 -1
- package/dist/shared/providers/shell/index.d.ts +1 -1
- package/dist/shared-agent-tools.d.ts +2 -0
- package/dist/shared-agent-tools.js +16 -0
- package/dist/shared-assistant.js +285 -285
- package/dist/shared-providers-shell.js +20 -20
- package/dist/ui/alert-dialog.js +2 -0
- package/package.json +7 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
//#region src/shared/agent-tools/errors.ts
|
|
2
|
+
var e = Symbol.for("burdenoff.fe-libs.agentToolError.v1"), t = new Set([
|
|
3
|
+
"INVALID_INPUT",
|
|
4
|
+
"UNAUTHENTICATED",
|
|
5
|
+
"FORBIDDEN",
|
|
6
|
+
"NOT_FOUND",
|
|
7
|
+
"CONFLICT",
|
|
8
|
+
"NEEDS_USER",
|
|
9
|
+
"CANCELLED",
|
|
10
|
+
"FAILED"
|
|
11
|
+
]), n = class extends Error {
|
|
12
|
+
code;
|
|
13
|
+
retryable;
|
|
14
|
+
[e] = !0;
|
|
15
|
+
constructor(e, t, n) {
|
|
16
|
+
super(t), this.name = "AgentToolError", this.code = e, this.retryable = n?.retryable ?? (e === "FAILED" || e === "INVALID_INPUT");
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
function r(r) {
|
|
20
|
+
if (r instanceof n) return !0;
|
|
21
|
+
if (!r || typeof r != "object") return !1;
|
|
22
|
+
let i = r;
|
|
23
|
+
return i[e] === !0 && typeof i.code == "string" && t.has(i.code) && typeof i.message == "string" && typeof i.retryable == "boolean";
|
|
24
|
+
}
|
|
25
|
+
var i = {
|
|
26
|
+
UNAUTHENTICATED: "UNAUTHENTICATED",
|
|
27
|
+
UNAUTHORIZED: "UNAUTHENTICATED",
|
|
28
|
+
FORBIDDEN: "FORBIDDEN",
|
|
29
|
+
PERMISSION_DENIED: "FORBIDDEN",
|
|
30
|
+
NOT_FOUND: "NOT_FOUND",
|
|
31
|
+
CONFLICT: "CONFLICT",
|
|
32
|
+
BAD_USER_INPUT: "INVALID_INPUT",
|
|
33
|
+
GRAPHQL_VALIDATION_FAILED: "INVALID_INPUT"
|
|
34
|
+
}, a = {
|
|
35
|
+
INVALID_INPUT: "The input was not accepted.",
|
|
36
|
+
UNAUTHENTICATED: "The person is signed out. Ask them to sign in again.",
|
|
37
|
+
FORBIDDEN: "The person does not have permission to do this here.",
|
|
38
|
+
NOT_FOUND: "Nothing with that id was found in the current workspace.",
|
|
39
|
+
CONFLICT: "The record is not in a state that allows this.",
|
|
40
|
+
NEEDS_USER: "Only the person can do this step.",
|
|
41
|
+
CANCELLED: "The action was cancelled.",
|
|
42
|
+
FAILED: "The action failed. Try again, or ask the person to do it in the page."
|
|
43
|
+
};
|
|
44
|
+
function o(e) {
|
|
45
|
+
if (!e || typeof e != "object") return [];
|
|
46
|
+
let t = e;
|
|
47
|
+
for (let e of [
|
|
48
|
+
t.graphQLErrors,
|
|
49
|
+
t.errors,
|
|
50
|
+
t.response?.errors
|
|
51
|
+
]) if (Array.isArray(e) && e.length > 0) return e;
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
function s(e) {
|
|
55
|
+
return !!e && typeof e == "object" && "name" in e && e.name === "AbortError";
|
|
56
|
+
}
|
|
57
|
+
function c(e) {
|
|
58
|
+
if (r(e)) return {
|
|
59
|
+
ok: !1,
|
|
60
|
+
error: {
|
|
61
|
+
code: e.code,
|
|
62
|
+
message: e.message,
|
|
63
|
+
retryable: e.retryable
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
if (s(e)) return {
|
|
67
|
+
ok: !1,
|
|
68
|
+
error: {
|
|
69
|
+
code: "CANCELLED",
|
|
70
|
+
message: a.CANCELLED,
|
|
71
|
+
retryable: !1
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
for (let t of o(e)) {
|
|
75
|
+
let e = t.extensions?.code, n = typeof e == "string" ? i[e] : void 0;
|
|
76
|
+
if (n) return {
|
|
77
|
+
ok: !1,
|
|
78
|
+
error: {
|
|
79
|
+
code: n,
|
|
80
|
+
message: a[n],
|
|
81
|
+
retryable: n === "INVALID_INPUT"
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
ok: !1,
|
|
87
|
+
error: {
|
|
88
|
+
code: "FAILED",
|
|
89
|
+
message: a.FAILED,
|
|
90
|
+
retryable: !0
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function l(e, t = a[e], n = !1) {
|
|
95
|
+
return {
|
|
96
|
+
ok: !1,
|
|
97
|
+
error: {
|
|
98
|
+
code: e,
|
|
99
|
+
message: t,
|
|
100
|
+
retryable: n
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
export { n as AgentToolError, l as agentToolFailure, r as isAgentToolError, c as toAgentToolFailure };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ★ BOFF-9783: agent tools — actions AI agents may take in the page, defined
|
|
3
|
+
* once and offered through WebMCP and the in-app assistant.
|
|
4
|
+
*
|
|
5
|
+
* ```tsx
|
|
6
|
+
* // once, in the app shell
|
|
7
|
+
* <AgentToolsProvider product="fluidgrids" gatherPageContext={gatherPageContext} />
|
|
8
|
+
*
|
|
9
|
+
* // in the component that owns the state a tool acts on
|
|
10
|
+
* const RUN_TOOLS = [defineAgentTool({ name: 'fg_run_get', … })];
|
|
11
|
+
* useAgentTools(RUN_TOOLS);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export * from './types';
|
|
15
|
+
export * from './limits';
|
|
16
|
+
export * from './errors';
|
|
17
|
+
export * from './defineAgentTool';
|
|
18
|
+
export * from './outputBudget';
|
|
19
|
+
export * from './registry';
|
|
20
|
+
export * from './runAgentTool';
|
|
21
|
+
export * from './useAgentTools';
|
|
22
|
+
export * from './confirm/confirmStore';
|
|
23
|
+
export * from './confirm/AgentConfirmHost';
|
|
24
|
+
export * from './adapters/webmcp';
|
|
25
|
+
export * from './adapters/assistant';
|
|
26
|
+
export * from './shell/shellTools';
|
|
27
|
+
export * from './ui/uiTools';
|
|
28
|
+
export { DENY_SELECTOR, INTERACTIVE_SELECTOR, accessibleName, isDenied, isSensitiveField, roleOf, } from './ui/dom';
|
|
29
|
+
export * from './AgentToolsProvider';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Size limits for agent tools.
|
|
3
|
+
*
|
|
4
|
+
* The first four are Chrome's published WebMCP guidance
|
|
5
|
+
* (developer.chrome.com/docs/ai/webmcp/secure-tools): not enforced by the
|
|
6
|
+
* browser, but every tool here keeps to them so an agent's context stays small
|
|
7
|
+
* and a tool reads the same in every channel.
|
|
8
|
+
*/
|
|
9
|
+
/** Tool names and parameter names. */
|
|
10
|
+
export declare const AGENT_TOOL_NAME_MAX = 30;
|
|
11
|
+
/** A tool's description. */
|
|
12
|
+
export declare const AGENT_TOOL_DESCRIPTION_MAX = 500;
|
|
13
|
+
/** A parameter's description. */
|
|
14
|
+
export declare const AGENT_TOOL_PARAM_DESCRIPTION_MAX = 150;
|
|
15
|
+
/** One result, as serialized JSON (UTF-8 bytes). */
|
|
16
|
+
export declare const AGENT_TOOL_OUTPUT_MAX_BYTES = 1536;
|
|
17
|
+
/** Longest string any single field of a result keeps before it is clamped. */
|
|
18
|
+
export declare const AGENT_TOOL_OUTPUT_STRING_MAX = 300;
|
|
19
|
+
/** `^[a-z][a-z0-9_]*$` — WebMCP allows more, this is the house style. */
|
|
20
|
+
export declare const AGENT_TOOL_NAME_PATTERN: RegExp;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
//#region src/shared/agent-tools/limits.ts
|
|
2
|
+
var e = 30, t = 500, n = 150, r = 1536, i = 300, a = /^[a-z][a-z0-9_]*$/;
|
|
3
|
+
//#endregion
|
|
4
|
+
export { t as AGENT_TOOL_DESCRIPTION_MAX, e as AGENT_TOOL_NAME_MAX, a as AGENT_TOOL_NAME_PATTERN, r as AGENT_TOOL_OUTPUT_MAX_BYTES, i as AGENT_TOOL_OUTPUT_STRING_MAX, n as AGENT_TOOL_PARAM_DESCRIPTION_MAX };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AgentToolErrorCode, AgentToolInitiator } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* One line per tool call: which tool, who asked, how it ended, how long it took.
|
|
4
|
+
*
|
|
5
|
+
* ★ Never the input or the result. Inputs carry what a person typed or an agent
|
|
6
|
+
* made up; results carry workspace data. Neither belongs in a log.
|
|
7
|
+
*/
|
|
8
|
+
export declare function logAgentToolCall(entry: {
|
|
9
|
+
tool: string;
|
|
10
|
+
initiator: AgentToolInitiator;
|
|
11
|
+
outcome: 'ok' | AgentToolErrorCode;
|
|
12
|
+
durationMs: number;
|
|
13
|
+
truncated?: boolean;
|
|
14
|
+
}): void;
|
|
15
|
+
export declare function logAgentToolProblem(message: string, context: Record<string, unknown>): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { logManager as e } from "../logger/LogManager.js";
|
|
2
|
+
//#region src/shared/agent-tools/log.ts
|
|
3
|
+
var t = e.getLogger("agent-tools");
|
|
4
|
+
function n(e) {
|
|
5
|
+
e.outcome === "ok" ? t.info("agent tool call", e) : t.warn("agent tool call", e);
|
|
6
|
+
}
|
|
7
|
+
function r(e, n) {
|
|
8
|
+
t.warn(e, n);
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { n as logAgentToolCall, r as logAgentToolProblem };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface CappedResult {
|
|
2
|
+
data: unknown;
|
|
3
|
+
truncated: boolean;
|
|
4
|
+
}
|
|
5
|
+
/** Serialized size in UTF-8 bytes — what the agent's context actually pays for. */
|
|
6
|
+
export declare function jsonByteLength(value: unknown): number;
|
|
7
|
+
/**
|
|
8
|
+
* Fit a tool's result into the output budget.
|
|
9
|
+
*
|
|
10
|
+
* Tools are written to return small results; this is the backstop for the one
|
|
11
|
+
* that does not (a long list, a long error text). In order, until it fits:
|
|
12
|
+
* 1. clamp every string to `AGENT_TOOL_OUTPUT_STRING_MAX` characters;
|
|
13
|
+
* 2. drop items from the END of the longest array, repeatedly — a list keeps
|
|
14
|
+
* its first, most relevant rows;
|
|
15
|
+
* 3. give up on the payload and say so, so the agent narrows its query instead
|
|
16
|
+
* of acting on a silently partial object.
|
|
17
|
+
*
|
|
18
|
+
* A value that cannot be serialized (a cycle, a BigInt) is a tool bug: it
|
|
19
|
+
* throws, and `runAgentTool` reports `FAILED`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function capResult(value: unknown, maxBytes?: number): CappedResult;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AGENT_TOOL_OUTPUT_MAX_BYTES as e } from "./limits.js";
|
|
2
|
+
//#region src/shared/agent-tools/outputBudget.ts
|
|
3
|
+
var t = new TextEncoder();
|
|
4
|
+
function n(e) {
|
|
5
|
+
return t.encode(JSON.stringify(e) ?? "null").length;
|
|
6
|
+
}
|
|
7
|
+
function r(t, r = e) {
|
|
8
|
+
let o = JSON.parse(JSON.stringify(t ?? null));
|
|
9
|
+
if (n(o) <= r) return {
|
|
10
|
+
data: o,
|
|
11
|
+
truncated: !1
|
|
12
|
+
};
|
|
13
|
+
let s = i(o);
|
|
14
|
+
if (n(s) <= r) return {
|
|
15
|
+
data: s,
|
|
16
|
+
truncated: !0
|
|
17
|
+
};
|
|
18
|
+
for (let e = 0; e < 1e4; e += 1) {
|
|
19
|
+
let e = a(s);
|
|
20
|
+
if (!e || e.length === 0) break;
|
|
21
|
+
if (e.pop(), n(s) <= r) return {
|
|
22
|
+
data: s,
|
|
23
|
+
truncated: !0
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
data: { note: "The result was too large to return. Narrow the request (filters, a smaller limit)." },
|
|
28
|
+
truncated: !0
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function i(e) {
|
|
32
|
+
if (typeof e == "string") return e.length > 300 ? `${e.slice(0, 299)}…` : e;
|
|
33
|
+
if (Array.isArray(e)) return e.map(i);
|
|
34
|
+
if (e && typeof e == "object") {
|
|
35
|
+
let t = {};
|
|
36
|
+
for (let [n, r] of Object.entries(e)) t[n] = i(r);
|
|
37
|
+
return t;
|
|
38
|
+
}
|
|
39
|
+
return e;
|
|
40
|
+
}
|
|
41
|
+
function a(e) {
|
|
42
|
+
let t = null, r = -1, i = (e) => {
|
|
43
|
+
if (Array.isArray(e)) {
|
|
44
|
+
let a = n(e);
|
|
45
|
+
e.length > 0 && a > r && (t = e, r = a), e.forEach(i);
|
|
46
|
+
} else e && typeof e == "object" && Object.values(e).forEach(i);
|
|
47
|
+
};
|
|
48
|
+
return i(e), t;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { r as capResult, n as jsonByteLength };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AnyDefinedAgentTool } from './types';
|
|
2
|
+
type Listener = () => void;
|
|
3
|
+
/** Register `tool`. Returns the cleanup that removes exactly this registration. */
|
|
4
|
+
export declare function registerAgentTool(tool: AnyDefinedAgentTool): () => void;
|
|
5
|
+
/** The tools in force (newest per name), sorted by name. Stable between changes. */
|
|
6
|
+
export declare function listAgentTools(): readonly AnyDefinedAgentTool[];
|
|
7
|
+
/** The tool in force for `name`, or null. */
|
|
8
|
+
export declare function getAgentTool(name: string): AnyDefinedAgentTool | null;
|
|
9
|
+
/** Called after every change to the set of tools in force. */
|
|
10
|
+
export declare function subscribeAgentTools(listener: Listener): () => void;
|
|
11
|
+
/** Test seam: forget every registration. */
|
|
12
|
+
export declare function clearAgentTools(): void;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//#region src/shared/agent-tools/registry.ts
|
|
2
|
+
var e = Symbol.for("burdenoff.fe-libs.agentTools.registry.v1");
|
|
3
|
+
function t() {
|
|
4
|
+
let t = globalThis, n = t[e];
|
|
5
|
+
if (n && n.stacks instanceof Map) return n;
|
|
6
|
+
let r = {
|
|
7
|
+
stacks: /* @__PURE__ */ new Map(),
|
|
8
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
9
|
+
snapshot: []
|
|
10
|
+
};
|
|
11
|
+
return t[e] = r, r;
|
|
12
|
+
}
|
|
13
|
+
function n() {
|
|
14
|
+
let e = t(), n = [];
|
|
15
|
+
for (let t of e.stacks.values()) {
|
|
16
|
+
let e = t[t.length - 1];
|
|
17
|
+
e && n.push(e.tool);
|
|
18
|
+
}
|
|
19
|
+
n.sort((e, t) => e.name.localeCompare(t.name)), e.snapshot = n;
|
|
20
|
+
for (let t of e.listeners) t();
|
|
21
|
+
}
|
|
22
|
+
function r(e) {
|
|
23
|
+
let { stacks: r } = t(), i = Symbol(e.name), a = r.get(e.name) ?? [];
|
|
24
|
+
a.push({
|
|
25
|
+
token: i,
|
|
26
|
+
tool: e
|
|
27
|
+
}), r.set(e.name, a), n();
|
|
28
|
+
let o = !1;
|
|
29
|
+
return () => {
|
|
30
|
+
if (o) return;
|
|
31
|
+
o = !0;
|
|
32
|
+
let r = t().stacks.get(e.name);
|
|
33
|
+
if (!r) return;
|
|
34
|
+
let a = r.findIndex((e) => e.token === i);
|
|
35
|
+
a < 0 || (r.splice(a, 1), r.length === 0 && t().stacks.delete(e.name), n());
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function i() {
|
|
39
|
+
return t().snapshot;
|
|
40
|
+
}
|
|
41
|
+
function a(e) {
|
|
42
|
+
let n = t().stacks.get(e);
|
|
43
|
+
return n?.[n.length - 1]?.tool ?? null;
|
|
44
|
+
}
|
|
45
|
+
function o(e) {
|
|
46
|
+
let { listeners: n } = t();
|
|
47
|
+
return n.add(e), () => n.delete(e);
|
|
48
|
+
}
|
|
49
|
+
function s() {
|
|
50
|
+
t().stacks.clear(), n();
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { s as clearAgentTools, a as getAgentTool, i as listAgentTools, r as registerAgentTool, o as subscribeAgentTools };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AgentToolInitiator, AgentToolResult, AnyDefinedAgentTool } from './types';
|
|
2
|
+
export interface RunAgentToolOptions {
|
|
3
|
+
initiator: AgentToolInitiator;
|
|
4
|
+
/** Cancels the call: a pending confirmation closes, the tool's own requests abort. */
|
|
5
|
+
signal: AbortSignal;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Run a tool for an agent — the ONE path every channel uses, so validation,
|
|
9
|
+
* confirmation, cancellation and the output budget are the same whichever
|
|
10
|
+
* agent asked.
|
|
11
|
+
*
|
|
12
|
+
* 1. Validate the input against the tool's strict schema. Wrong types, missing
|
|
13
|
+
* or unknown fields, oversized strings and out-of-range numbers are refused
|
|
14
|
+
* with a message naming the field, so the agent can correct and retry.
|
|
15
|
+
* 2. A consequential tool asks the person first; declining is `CANCELLED`.
|
|
16
|
+
* 3. Execute with the call's AbortSignal.
|
|
17
|
+
* 4. Cap the result.
|
|
18
|
+
*
|
|
19
|
+
* Never throws: every outcome is an `AgentToolResult`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function runAgentTool(tool: AnyDefinedAgentTool, rawInput: unknown, { initiator, signal }: RunAgentToolOptions): Promise<AgentToolResult>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { AgentToolError as e, agentToolFailure as t, toAgentToolFailure as n } from "./errors.js";
|
|
2
|
+
import { logAgentToolCall as r } from "./log.js";
|
|
3
|
+
import { requestAgentConfirmation as i } from "./confirm/confirmStore.js";
|
|
4
|
+
import { capResult as a } from "./outputBudget.js";
|
|
5
|
+
//#region src/shared/agent-tools/runAgentTool.ts
|
|
6
|
+
var o = 5;
|
|
7
|
+
async function s(o, s, { initiator: u, signal: d }) {
|
|
8
|
+
let f = Date.now(), p = (e) => (r({
|
|
9
|
+
tool: o.name,
|
|
10
|
+
initiator: u,
|
|
11
|
+
outcome: e.ok ? "ok" : e.error.code,
|
|
12
|
+
durationMs: Date.now() - f,
|
|
13
|
+
...e.ok && e.truncated ? { truncated: !0 } : {}
|
|
14
|
+
}), e);
|
|
15
|
+
if (d.aborted) return p(t("CANCELLED"));
|
|
16
|
+
let m = o.input.safeParse(c(s));
|
|
17
|
+
if (!m.success) return p(t("INVALID_INPUT", l(m.error), !0));
|
|
18
|
+
let h = m.data;
|
|
19
|
+
try {
|
|
20
|
+
if (o.annotations?.consequential) {
|
|
21
|
+
let n = o.confirm?.(h);
|
|
22
|
+
if (!n) throw new e("NEEDS_USER", "This action needs the person to do it.");
|
|
23
|
+
if (!await i(o.title, n, d)) return p(t("CANCELLED", "The person declined this action.", !1));
|
|
24
|
+
}
|
|
25
|
+
let n = await o.execute(h, {
|
|
26
|
+
signal: d,
|
|
27
|
+
initiator: u
|
|
28
|
+
});
|
|
29
|
+
if (d.aborted) return p(t("CANCELLED"));
|
|
30
|
+
let { data: r, truncated: s } = a(n);
|
|
31
|
+
return p(s ? {
|
|
32
|
+
ok: !0,
|
|
33
|
+
data: r,
|
|
34
|
+
truncated: !0
|
|
35
|
+
} : {
|
|
36
|
+
ok: !0,
|
|
37
|
+
data: r
|
|
38
|
+
});
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return p(n(e));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function c(e) {
|
|
44
|
+
if (e == null) return {};
|
|
45
|
+
if (typeof e == "string") try {
|
|
46
|
+
return JSON.parse(e);
|
|
47
|
+
} catch {
|
|
48
|
+
return e;
|
|
49
|
+
}
|
|
50
|
+
return e;
|
|
51
|
+
}
|
|
52
|
+
function l(e) {
|
|
53
|
+
let t = e.issues.slice(0, o).map((e) => `${e.path.length > 0 ? e.path.join(".") : "input"}: ${e.message}`), n = e.issues.length - o;
|
|
54
|
+
return n > 0 && t.push(`…and ${n} more`), `Invalid input. ${t.join("; ")}`;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { s as runAgentTool };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { LocalDestination } from '../../assistant/localUiCommands';
|
|
2
|
+
import { AnyDefinedAgentTool } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* ★ BOFF-9783: the tools every product shell offers, whatever the product —
|
|
5
|
+
* where am I, take me somewhere, switch the theme. They read live shell state
|
|
6
|
+
* through `ShellToolsSource`, which `AgentToolsProvider` refreshes on every
|
|
7
|
+
* render, so a tool never acts on a stale workspace or navigation tree.
|
|
8
|
+
*/
|
|
9
|
+
export interface ShellToolsSource {
|
|
10
|
+
/** Product id, e.g. the assistant's product namespace. */
|
|
11
|
+
product: string;
|
|
12
|
+
organizationId: string | null;
|
|
13
|
+
workspaceId: string | null;
|
|
14
|
+
projectId: string | null;
|
|
15
|
+
/** The signed-in person's display name, if the shell wants agents to know it. Never an email. */
|
|
16
|
+
userDisplayName: string | null;
|
|
17
|
+
/**
|
|
18
|
+
* The product's description of the current page (the assistant's
|
|
19
|
+
* `gatherPageContext`). Only the entity keys below are passed on.
|
|
20
|
+
*/
|
|
21
|
+
pageContext: () => Readonly<Record<string, unknown>> | null;
|
|
22
|
+
/** The places the person's own navigation can take them (permission-filtered). */
|
|
23
|
+
destinations: readonly LocalDestination[];
|
|
24
|
+
/** False while permissions load or a role is being simulated: no navigation then. */
|
|
25
|
+
navigationReady: boolean;
|
|
26
|
+
/** Navigate within the app. */
|
|
27
|
+
navigate: (path: string) => void;
|
|
28
|
+
/** Current theme mode, or null without a theme provider. */
|
|
29
|
+
themeMode: string | null;
|
|
30
|
+
setThemeMode: ((mode: 'light' | 'dark' | 'system') => void) | null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve what an agent asked for to a destination the person's own navigation
|
|
34
|
+
* offers. A free path is accepted only AT or BELOW a path the navigation lists
|
|
35
|
+
* (a record under a listed page), never a sibling the permission or persona
|
|
36
|
+
* filter removed, so an agent cannot reach a page the sidebar hides from this
|
|
37
|
+
* person.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveDestination(destinations: readonly LocalDestination[], requested: string): {
|
|
40
|
+
path: string;
|
|
41
|
+
label: string;
|
|
42
|
+
} | null;
|
|
43
|
+
export declare function assertNoUnsavedChanges(): void;
|
|
44
|
+
export declare function createShellTools(getSource: () => ShellToolsSource): AnyDefinedAgentTool[];
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { isSafeInAppPath as e } from "../../navigation/inAppPath.js";
|
|
2
|
+
import { unsavedChangesBlocking as t } from "../../navigation/unsavedChanges.js";
|
|
3
|
+
import { AgentToolError as n } from "../errors.js";
|
|
4
|
+
import { defineAgentTool as r } from "../defineAgentTool.js";
|
|
5
|
+
import { z as i } from "zod";
|
|
6
|
+
//#region src/shared/agent-tools/shell/shellTools.ts
|
|
7
|
+
var a = [
|
|
8
|
+
"entityType",
|
|
9
|
+
"entityId",
|
|
10
|
+
"entityName",
|
|
11
|
+
"pageType",
|
|
12
|
+
"activeTab"
|
|
13
|
+
], o = 25;
|
|
14
|
+
function s(e) {
|
|
15
|
+
let t = {};
|
|
16
|
+
if (!e) return t;
|
|
17
|
+
for (let n of a) {
|
|
18
|
+
let r = e[n];
|
|
19
|
+
typeof r == "string" && r.trim() && (t[n] = r.slice(0, 120));
|
|
20
|
+
}
|
|
21
|
+
return t;
|
|
22
|
+
}
|
|
23
|
+
function c(e) {
|
|
24
|
+
try {
|
|
25
|
+
return e.pageContext();
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function l() {
|
|
31
|
+
if (typeof document > "u") return null;
|
|
32
|
+
let e = document.querySelector("main h1, h1")?.textContent?.replace(/\s+/g, " ").trim();
|
|
33
|
+
return e ? e.slice(0, 120) : null;
|
|
34
|
+
}
|
|
35
|
+
function u(t, n) {
|
|
36
|
+
let r = n.trim(), i = r.toLowerCase(), a = t.find((e) => e.label.toLowerCase() === i || e.id.toLowerCase() === i);
|
|
37
|
+
if (a) return {
|
|
38
|
+
path: a.path,
|
|
39
|
+
label: a.label
|
|
40
|
+
};
|
|
41
|
+
if (!r.startsWith("/") || !e(r)) return null;
|
|
42
|
+
let o = r.split(/[?#]/)[0] ?? r, s = null;
|
|
43
|
+
for (let e of t) for (let t of [e.path, ...e.aliases ?? []]) {
|
|
44
|
+
let n = t.split(/[?#]/)[0]?.replace(/\/+$/, "") ?? "";
|
|
45
|
+
n && (o === n || o.startsWith(`${n}/`)) && (!s || n.length > s.length) && (s = {
|
|
46
|
+
path: o === n ? e.path : r,
|
|
47
|
+
label: e.label,
|
|
48
|
+
length: n.length
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return s ? {
|
|
52
|
+
path: s.path,
|
|
53
|
+
label: s.label
|
|
54
|
+
} : null;
|
|
55
|
+
}
|
|
56
|
+
function d(e) {
|
|
57
|
+
if (!e.navigationReady) throw new n("CONFLICT", "The app's navigation is still loading. Try again in a moment.", { retryable: !0 });
|
|
58
|
+
}
|
|
59
|
+
function f() {
|
|
60
|
+
let e = t("assistant");
|
|
61
|
+
if (e) throw new n("CONFLICT", `The page has unsaved changes (${e.map((e) => e.label).join(", ")}). Ask the person to save or discard them first.`);
|
|
62
|
+
}
|
|
63
|
+
function p(e) {
|
|
64
|
+
return [
|
|
65
|
+
r({
|
|
66
|
+
name: "app_get_context",
|
|
67
|
+
title: "Get current context",
|
|
68
|
+
description: "Returns where the person is in the app: product, current page path and heading, the record the page shows (entity type and id), and the active organization, workspace and project ids. Use it first to understand \"this\", \"here\" or \"current\".",
|
|
69
|
+
input: i.strictObject({}),
|
|
70
|
+
annotations: {
|
|
71
|
+
readOnly: !0,
|
|
72
|
+
untrustedContent: !0
|
|
73
|
+
},
|
|
74
|
+
execute: () => {
|
|
75
|
+
let t = e();
|
|
76
|
+
return {
|
|
77
|
+
product: t.product,
|
|
78
|
+
path: typeof window > "u" ? null : window.location.pathname,
|
|
79
|
+
title: typeof document > "u" ? null : document.title.slice(0, 120),
|
|
80
|
+
heading: l(),
|
|
81
|
+
...s(c(t)),
|
|
82
|
+
organizationId: t.organizationId,
|
|
83
|
+
workspaceId: t.workspaceId,
|
|
84
|
+
projectId: t.projectId,
|
|
85
|
+
...t.userDisplayName ? { userDisplayName: t.userDisplayName } : {}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}),
|
|
89
|
+
r({
|
|
90
|
+
name: "app_list_pages",
|
|
91
|
+
title: "List pages",
|
|
92
|
+
description: "Lists the pages the person's navigation offers (label and path). Use it to find where to go before calling app_navigate.",
|
|
93
|
+
input: i.strictObject({ offset: i.number().int().min(0).max(1e3).default(0).describe("Index of the first page to return, for paging.") }),
|
|
94
|
+
annotations: { readOnly: !0 },
|
|
95
|
+
execute: ({ offset: t }) => {
|
|
96
|
+
let n = e();
|
|
97
|
+
d(n);
|
|
98
|
+
let { destinations: r } = n, i = r.slice(t, t + o), a = t + i.length;
|
|
99
|
+
return {
|
|
100
|
+
pages: i.map((e) => ({
|
|
101
|
+
label: e.context ? `${e.context} › ${e.label}` : e.label,
|
|
102
|
+
path: e.path
|
|
103
|
+
})),
|
|
104
|
+
...a < r.length ? { nextOffset: a } : {}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}),
|
|
108
|
+
r({
|
|
109
|
+
name: "app_navigate",
|
|
110
|
+
title: "Open a page",
|
|
111
|
+
description: "Opens a page of the app in the person's tab. Pass a page label from app_list_pages (e.g. \"Runs\") or an in-app path starting with \"/\". Refused while the current page has unsaved changes.",
|
|
112
|
+
input: i.strictObject({ destination: i.string().trim().min(1).max(200).describe("A page label from app_list_pages, or an in-app path starting with \"/\".") }),
|
|
113
|
+
execute: ({ destination: t }) => {
|
|
114
|
+
let r = e();
|
|
115
|
+
d(r);
|
|
116
|
+
let i = u(r.destinations, t);
|
|
117
|
+
if (!i) {
|
|
118
|
+
let e = r.destinations.slice(0, 12).map((e) => e.label).join(", ");
|
|
119
|
+
throw new n("NOT_FOUND", `No page "${t.slice(0, 60)}" in this app's navigation. Known pages include: ${e}. Use app_list_pages for all.`, { retryable: !0 });
|
|
120
|
+
}
|
|
121
|
+
return f(), r.navigate(i.path), {
|
|
122
|
+
navigatedTo: i.path,
|
|
123
|
+
page: i.label
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}),
|
|
127
|
+
r({
|
|
128
|
+
name: "app_set_theme",
|
|
129
|
+
title: "Change theme",
|
|
130
|
+
description: "Switches the app between light, dark and system appearance.",
|
|
131
|
+
input: i.strictObject({ mode: i.enum([
|
|
132
|
+
"light",
|
|
133
|
+
"dark",
|
|
134
|
+
"system"
|
|
135
|
+
]).describe("The appearance to switch to.") }),
|
|
136
|
+
execute: ({ mode: t }) => {
|
|
137
|
+
let { setThemeMode: r } = e();
|
|
138
|
+
if (!r) throw new n("NEEDS_USER", "This app does not let the theme be changed here.");
|
|
139
|
+
return r(t), { mode: t };
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
export { f as assertNoUnsavedChanges, p as createShellTools, u as resolveDestination };
|