@particle-academy/agent-integrations 0.51.0 → 0.52.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/CHANGELOG.md +40 -0
- package/README.md +58 -0
- package/dist/bridges/forms.d.cts +52 -7
- package/dist/bridges/forms.d.ts +52 -7
- package/dist/bridges-forms.cjs +112 -11
- package/dist/bridges-forms.cjs.map +1 -1
- package/dist/bridges-forms.js +2 -1
- package/dist/bridges-screens.cjs +4 -2
- package/dist/bridges-screens.cjs.map +1 -1
- package/dist/bridges-screens.js +2 -1
- package/dist/{chunk-CP3EHLZJ.js → chunk-7HHZBPDE.js} +3 -25
- package/dist/chunk-7HHZBPDE.js.map +1 -0
- package/dist/{chunk-DNF3QVTW.js → chunk-AL77RO25.js} +91 -13
- package/dist/chunk-AL77RO25.js.map +1 -0
- package/dist/chunk-DPBAEWB5.js +28 -0
- package/dist/chunk-DPBAEWB5.js.map +1 -0
- package/dist/index.cjs +115 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/docs/multi-form-hosts.md +97 -0
- package/package.json +1 -1
- package/dist/chunk-CP3EHLZJ.js.map +0 -1
- package/dist/chunk-DNF3QVTW.js.map +0 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { commit, hostErrorMessage } from './chunk-DPBAEWB5.js';
|
|
1
2
|
import { ensureUndoToolsRegistered } from './chunk-ZDKYKJFH.js';
|
|
2
3
|
import { wrapToolWithActivity } from './chunk-2VENWGLZ.js';
|
|
4
|
+
import { emitActivity } from './chunk-C3TYI5TJ.js';
|
|
3
5
|
import { textResult, errorResult } from './chunk-3GFKC6OK.js';
|
|
4
6
|
|
|
5
7
|
// src/bridges/forms.ts
|
|
@@ -12,6 +14,33 @@ function registerFormBridge(host, options) {
|
|
|
12
14
|
const bridgeId = `form:${adapter.id}`;
|
|
13
15
|
ensureUndoToolsRegistered(host, { defaultAgentId: agent.id });
|
|
14
16
|
const formId = adapter.id;
|
|
17
|
+
let disposed = false;
|
|
18
|
+
const onDispose = /* @__PURE__ */ new Set();
|
|
19
|
+
const CLOSED = /* @__PURE__ */ Symbol("form-closed");
|
|
20
|
+
const untilClosed = (p) => {
|
|
21
|
+
if (disposed) {
|
|
22
|
+
p.catch(() => {
|
|
23
|
+
});
|
|
24
|
+
return Promise.resolve(CLOSED);
|
|
25
|
+
}
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const close = () => resolve(CLOSED);
|
|
28
|
+
onDispose.add(close);
|
|
29
|
+
p.then(
|
|
30
|
+
(v) => {
|
|
31
|
+
onDispose.delete(close);
|
|
32
|
+
resolve(v);
|
|
33
|
+
},
|
|
34
|
+
(e) => {
|
|
35
|
+
onDispose.delete(close);
|
|
36
|
+
reject(e);
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
const closedUnknown = (what) => errorResult(
|
|
42
|
+
`Form "${formId}" was closed before the host confirmed ${what}, so whether it was applied is unknown. Do not assume it was. If the form is open again, read it back (form_get_values) before retrying.`
|
|
43
|
+
);
|
|
15
44
|
const target = (args) => ({
|
|
16
45
|
kind: "form",
|
|
17
46
|
screenId: adapter.screenId,
|
|
@@ -23,7 +52,7 @@ function registerFormBridge(host, options) {
|
|
|
23
52
|
try {
|
|
24
53
|
return await handler(args);
|
|
25
54
|
} catch (e) {
|
|
26
|
-
return errorResult(e
|
|
55
|
+
return errorResult(hostErrorMessage(e, `${name} failed.`));
|
|
27
56
|
}
|
|
28
57
|
};
|
|
29
58
|
const final = isMutation ? wrapToolWithActivity(wrapped, {
|
|
@@ -91,11 +120,15 @@ function registerFormBridge(host, options) {
|
|
|
91
120
|
value: { description: "Value to set. Type depends on the field's `type`." }
|
|
92
121
|
},
|
|
93
122
|
["field", "value"],
|
|
94
|
-
(args) => {
|
|
123
|
+
async (args) => {
|
|
95
124
|
const name = String(args.field ?? "");
|
|
96
125
|
const fieldDef = adapter.getFields().find((f) => f.name === name);
|
|
97
126
|
if (!fieldDef) return errorResult(`Unknown field: ${name}`);
|
|
98
|
-
|
|
127
|
+
const done = await untilClosed(
|
|
128
|
+
commit(() => adapter.setValue(name, args.value), `Host refused the write to "${name}".`)
|
|
129
|
+
);
|
|
130
|
+
if (done === CLOSED) return closedUnknown(`the write to "${name}"`);
|
|
131
|
+
if (!done.ok) return errorResult(done.error);
|
|
99
132
|
return textResult(`${name} \u2190 ${JSON.stringify(args.value)}`, { field: name, value: args.value });
|
|
100
133
|
},
|
|
101
134
|
true
|
|
@@ -105,16 +138,52 @@ function registerFormBridge(host, options) {
|
|
|
105
138
|
"Set multiple fields atomically. Pass a `values` object keyed by field name.",
|
|
106
139
|
{ values: { type: "object" } },
|
|
107
140
|
["values"],
|
|
108
|
-
(args) => {
|
|
141
|
+
async (args) => {
|
|
109
142
|
const values = args.values && typeof args.values === "object" ? args.values : {};
|
|
110
143
|
const fields = adapter.getFields();
|
|
111
144
|
const known = new Set(fields.map((f) => f.name));
|
|
112
145
|
const unknownKeys = Object.keys(values).filter((k) => !known.has(k));
|
|
113
146
|
if (unknownKeys.length) return errorResult(`Unknown fields: ${unknownKeys.join(", ")}`);
|
|
114
|
-
|
|
115
|
-
|
|
147
|
+
const setValues = adapter.setValues;
|
|
148
|
+
if (setValues) {
|
|
149
|
+
const done = await untilClosed(commit(() => setValues(values), "Host refused the write."));
|
|
150
|
+
if (done === CLOSED) return closedUnknown("the write");
|
|
151
|
+
if (!done.ok) return errorResult(done.error);
|
|
116
152
|
} else {
|
|
117
|
-
|
|
153
|
+
const keys = Object.keys(values);
|
|
154
|
+
const committed = [];
|
|
155
|
+
for (const [i, k] of keys.entries()) {
|
|
156
|
+
const done = await untilClosed(
|
|
157
|
+
commit(() => adapter.setValue(k, values[k]), `Host refused the write to "${k}".`)
|
|
158
|
+
);
|
|
159
|
+
if (done === CLOSED) {
|
|
160
|
+
return {
|
|
161
|
+
...closedUnknown(`the write to "${k}"`),
|
|
162
|
+
structuredContent: { committed, pending: k, notAttempted: keys.slice(i + 1) }
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
if (!done.ok) {
|
|
166
|
+
const state = { committed, failed: k, notAttempted: keys.slice(i + 1) };
|
|
167
|
+
if (committed.length) {
|
|
168
|
+
emitActivity({
|
|
169
|
+
agentId: agent.id,
|
|
170
|
+
agentName: agent.name,
|
|
171
|
+
agentColor: agent.color,
|
|
172
|
+
target: target({}),
|
|
173
|
+
action: "form_set_values",
|
|
174
|
+
timestamp: Date.now(),
|
|
175
|
+
meta: state
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
...errorResult(
|
|
180
|
+
`"${k}" was refused: ${done.error} ` + (committed.length ? `Already committed and NOT rolled back: ${committed.join(", ")}. ` : "Nothing was committed. ") + (state.notAttempted.length ? `Not attempted: ${state.notAttempted.join(", ")}.` : "")
|
|
181
|
+
),
|
|
182
|
+
structuredContent: state
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
committed.push(k);
|
|
186
|
+
}
|
|
118
187
|
}
|
|
119
188
|
return textResult(`Set ${Object.keys(values).length} field(s)`, { values });
|
|
120
189
|
},
|
|
@@ -144,16 +213,22 @@ function registerFormBridge(host, options) {
|
|
|
144
213
|
async () => {
|
|
145
214
|
if (!adapter.submit) return errorResult("Host did not provide a submit implementation.");
|
|
146
215
|
if (pendingMode && adapter.confirm) {
|
|
147
|
-
const
|
|
216
|
+
const confirm = adapter.confirm;
|
|
217
|
+
const ok = await untilClosed(Promise.resolve().then(() => confirm({
|
|
148
218
|
action: "submit",
|
|
149
219
|
form: formId,
|
|
150
220
|
title: adapter.title,
|
|
151
221
|
values: adapter.getValues()
|
|
152
|
-
});
|
|
222
|
+
})));
|
|
223
|
+
if (ok === CLOSED) {
|
|
224
|
+
return errorResult(`Form "${formId}" was closed before the submit was confirmed. Nothing was submitted.`);
|
|
225
|
+
}
|
|
153
226
|
if (!ok) return errorResult("Declined by user");
|
|
154
227
|
}
|
|
155
|
-
const
|
|
156
|
-
|
|
228
|
+
const submit = adapter.submit;
|
|
229
|
+
const result = await untilClosed(Promise.resolve().then(() => submit()));
|
|
230
|
+
if (result === CLOSED) return closedUnknown("the submit");
|
|
231
|
+
if (!result.ok) return errorResult(hostErrorMessage(result.error, "Submit failed"));
|
|
157
232
|
return textResult("Submitted", { values: result.values });
|
|
158
233
|
},
|
|
159
234
|
true
|
|
@@ -162,11 +237,14 @@ function registerFormBridge(host, options) {
|
|
|
162
237
|
id: bridgeId,
|
|
163
238
|
title: adapter.title ?? formId,
|
|
164
239
|
dispose: () => {
|
|
240
|
+
disposed = true;
|
|
165
241
|
for (const d of disposers) d();
|
|
242
|
+
for (const close of [...onDispose]) close();
|
|
243
|
+
onDispose.clear();
|
|
166
244
|
}
|
|
167
245
|
};
|
|
168
246
|
}
|
|
169
247
|
|
|
170
248
|
export { registerFormBridge };
|
|
171
|
-
//# sourceMappingURL=chunk-
|
|
172
|
-
//# sourceMappingURL=chunk-
|
|
249
|
+
//# sourceMappingURL=chunk-AL77RO25.js.map
|
|
250
|
+
//# sourceMappingURL=chunk-AL77RO25.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bridges/forms.ts"],"names":[],"mappings":"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","file":"chunk-AL77RO25.js","sourcesContent":["import { textResult, errorResult } from \"../mcp/server\";\nimport type { ToolHost } from \"../mcp/tool-host\";\nimport type { JsonObject } from \"../mcp/types\";\nimport type { Bridge } from \"./types\";\nimport { wrapToolWithActivity } from \"../presence/wrap-tool-with-activity\";\nimport { ensureUndoToolsRegistered } from \"../undo/undo-tools\";\nimport type { AgentTarget } from \"../presence/types\";\nimport { emitActivity } from \"../presence/registry\";\nimport { commit, hostErrorMessage } from \"./host-commit\";\n\n/**\n * Field descriptor — what the host says about each form field. Mirrors the\n * subset of HTML / react-fancy input shapes agents care about.\n */\nexport type FormFieldDescriptor = {\n /** Form-local field name. Matches the key the host uses in its values map. */\n name: string;\n /** Display label. */\n label?: string;\n /** Logical type. Drives validation + agent expectations. */\n type:\n | \"text\"\n | \"textarea\"\n | \"number\"\n | \"email\"\n | \"password\"\n | \"url\"\n | \"date\"\n | \"select\"\n | \"multi-select\"\n | \"checkbox\"\n | \"switch\"\n | \"radio\"\n | \"file\"\n | \"json\";\n /** Allowed values for select / radio. */\n options?: Array<{ value: string; label: string }>;\n /** Whether the field must be filled before submit. */\n required?: boolean;\n /** Free-text hint surfaced to the agent. */\n description?: string;\n /** Default value (presented in get_value when nothing set). */\n defaultValue?: unknown;\n};\n\n/**\n * Adapter the host wires per form. Matches the controlled-state pattern\n * react-fancy already uses (`value` / `onValueChange`); this adapter just\n * exposes those state slots in a form-shaped way the bridge can call.\n *\n * `setValue` / `setValues` may be sync (`void`) or async (a Promise); see\n * {@link FormAdapterCommit}. Return a Promise when the host's write is\n * authoritative — a persisted draft, or one the host can refuse or report\n * stale — so the agent is told the truth instead of \"success\" before anything\n * was accepted.\n *\n * This bridge pushes NO undo entries: `agent_undo` cannot revert a form write.\n */\nexport type FormBridgeAdapter = {\n /** Stable id for the form (used in form_describe + presence target). */\n id: string;\n /** Display title for human-readable logs. */\n title?: string;\n /** Optional fancy-screens screen id this form belongs to. */\n screenId?: string;\n /** Field descriptors. The bridge uses these for schema introspection\n * (form_describe). Agents call this first to know what to fill. */\n getFields: () => FormFieldDescriptor[];\n /** Read a single field's current value. */\n getValue: (name: string) => unknown;\n /** Read all values as { fieldName: value }. */\n getValues: () => Record<string, unknown>;\n /**\n * Set a single field's value. The host wires this to its setState, or to\n * an authoritative write it awaits. Sync `void` behaves as it always has; a\n * Promise is awaited, and a rejection or `{ ok: false }` is the tool's error.\n */\n setValue: (name: string, value: unknown) => FormAdapterCommit;\n /**\n * Set many at once, ideally as one host commit. Same sync/async contract as\n * `setValue`. When absent, the bridge awaits `setValue` once per field in\n * order and stops at the first refusal (so that path is NOT atomic).\n */\n setValues?: (values: Record<string, unknown>) => FormAdapterCommit;\n /** Programmatically focus a field (host implements DOM focus). Optional. */\n focus?: (name: string) => void;\n /** Submit the form. Returns the values that were submitted (or rejection). */\n submit?: () => Promise<{ ok: boolean; values?: Record<string, unknown>; error?: string }>;\n /**\n * Human confirm gate for `form_submit` (trust-but-verify). When `pendingMode`\n * is on, the bridge calls this before submitting; return false to decline.\n * Wire it to a human control — submitting a form is a human-visible,\n * often-destructive action (checkout, delete, invite).\n */\n confirm?: (request: FormConfirmRequest) => Promise<boolean> | boolean;\n};\n\n/**\n * What an async `setValue` / `setValues` may resolve to, to acknowledge (or\n * refuse) a write. `{ ok: false }` is a refusal the agent can act on: `error`\n * is shown to the agent verbatim, so keep it a human-readable sentence (e.g.\n * \"Draft changed since you read it; re-read and retry\"), never a stack trace or\n * an internal path. `code` (e.g. `\"stale\"`, `\"forbidden\"`) is for the host's own\n * use; it is not shown to the agent.\n */\nexport type FormAdapterResult = { ok: true } | { ok: false; error: string; code?: string };\n\n/**\n * Return type of `setValue` / `setValues`. A sync `void` return keeps the\n * original behaviour. A Promise makes the bridge WAIT: the tool reports\n * success, and emits its AgentActivity event, only after the promise resolves\n * (to `void` or `{ ok: true }`). A rejection or `{ ok: false }` becomes that\n * tool call's error result and emits no activity. If the bridge is disposed\n * while the write is pending, the call settles at once as an error saying the\n * outcome is unknown, and a later settle is ignored.\n */\nexport type FormAdapterCommit = void | Promise<void | FormAdapterResult>;\n\n/** What a `form_submit` confirm gate is asked to approve. */\nexport type FormConfirmRequest = {\n action: \"submit\";\n form: string;\n title?: string;\n values: Record<string, unknown>;\n};\n\nexport type FormBridgeOptions = {\n adapter: FormBridgeAdapter;\n /** Identity tagged into activity events. */\n agent?: { id: string; name?: string; color?: string };\n /**\n * Stage `form_submit` for human confirmation via `adapter.confirm` instead of\n * submitting immediately. **Default: ON** (matches the navigation bridge and\n * the Human+ trust-but-verify contract). Set false only when auto-submit is\n * safe. If on without an `adapter.confirm`, submit proceeds (nothing to gate).\n */\n pendingMode?: boolean;\n};\n\nconst DEFAULT_AGENT = { id: \"agent\", name: \"Agent\", color: \"#a855f7\" };\n\n/**\n * registerFormBridge — wires schema-driven MCP access to a single form.\n *\n * ONE form bridge per host. The tool names (`form_set_value`, …) are not\n * scoped to the form id, so since 0.51.0 a second form bridge with a different\n * adapter id on the same host throws (the duplicate-registrant refusal); a\n * remount of the SAME form re-registers fine. For several forms, give each its\n * own host for now — see `docs/multi-form-hosts.md`.\n */\nexport function registerFormBridge(\n host: ToolHost,\n options: FormBridgeOptions,\n): Bridge {\n const { adapter } = options;\n const agent = { ...DEFAULT_AGENT, ...(options.agent ?? {}) };\n const pendingMode = options.pendingMode ?? true;\n const disposers: Array<() => void> = [];\n // The bridge id is also its registrant identity on the host: the same bridge\n // may re-register (a remount), a different one claiming a name is refused.\n const bridgeId = `form:${adapter.id}`;\n\n // agent_undo / agent_redo / agent_history are registered whenever any bridge\n // mounts, so undo availability doesn't hinge on which bridges are co-present.\n ensureUndoToolsRegistered(host, { defaultAgentId: agent.id });\n const formId = adapter.id;\n\n // Close handling. A host may dispose the bridge (the form unmounts) while a\n // write or a human confirm is still pending; its promise may then never\n // settle. Every awaited host call is raced against dispose, so the tool call\n // settles at once instead of hanging, and a late settle is ignored.\n let disposed = false;\n const onDispose = new Set<() => void>();\n const CLOSED: unique symbol = Symbol(\"form-closed\");\n const untilClosed = <T>(p: Promise<T>): Promise<T | typeof CLOSED> => {\n if (disposed) {\n p.catch(() => {}); // a late rejection must never be unhandled\n return Promise.resolve(CLOSED);\n }\n return new Promise<T | typeof CLOSED>((resolve, reject) => {\n const close = () => resolve(CLOSED);\n onDispose.add(close);\n p.then(\n (v) => { onDispose.delete(close); resolve(v); },\n (e) => { onDispose.delete(close); reject(e); },\n );\n });\n };\n const closedUnknown = (what: string) =>\n errorResult(\n `Form \"${formId}\" was closed before the host confirmed ${what}, so whether it was applied is unknown. ` +\n \"Do not assume it was. If the form is open again, read it back (form_get_values) before retrying.\",\n );\n\n const target =(args: any): AgentTarget => ({\n kind: \"form\",\n screenId: adapter.screenId,\n elementId: typeof args?.field === \"string\" ? `${formId}:${args.field}` : formId,\n label: typeof args?.field === \"string\"\n ? `${adapter.title ?? formId} → ${args.field}`\n : adapter.title ?? formId,\n });\n\n const reg = (\n name: string,\n description: string,\n properties: Record<string, unknown>,\n required: string[],\n handler: (args: JsonObject) => Promise<any> | any,\n isMutation: boolean,\n ) => {\n const wrapped = async (args: JsonObject) => {\n try {\n return await handler(args);\n } catch (e) {\n return errorResult(hostErrorMessage(e, `${name} failed.`));\n }\n };\n const final = isMutation\n ? wrapToolWithActivity(wrapped, {\n toolName: name,\n agent: { id: agent.id, name: agent.name, color: agent.color },\n kind: \"form\",\n screenId: adapter.screenId,\n resolveTarget: ({ args }) => target(args),\n })\n : wrapped;\n disposers.push(\n host.registerTool(\n {\n name,\n description,\n inputSchema: { type: \"object\", properties: properties as any, required, additionalProperties: false },\n },\n final as any,\n { owner: bridgeId },\n ),\n );\n };\n\n // ───────────── Read tools ─────────────\n\n reg(\n \"form_describe\",\n `Describe the form \"${formId}\" — fields, types, options, required flags. Call this first to know what's fillable.`,\n {},\n [],\n () => {\n const fields = adapter.getFields();\n const text = fields\n .map((f) => `${f.name}${f.required ? \"*\" : \"\"} (${f.type})${f.label ? ` — ${f.label}` : \"\"}`)\n .join(\"\\n\");\n return textResult(text || \"(no fields)\", { id: formId, title: adapter.title, fields });\n },\n false,\n );\n\n reg(\n \"form_get_value\",\n \"Read a single field's current value.\",\n { field: { type: \"string\" } },\n [\"field\"],\n (args) => {\n const name = String(args.field ?? \"\");\n if (!adapter.getFields().find((f) => f.name === name)) {\n return errorResult(`Unknown field: ${name}`);\n }\n const value = adapter.getValue(name);\n return textResult(JSON.stringify(value), { field: name, value });\n },\n false,\n );\n\n reg(\n \"form_get_values\",\n \"Read every field's current value as a JSON object.\",\n {},\n [],\n () => {\n const values = adapter.getValues();\n return textResult(JSON.stringify(values, null, 2), values);\n },\n false,\n );\n\n // ───────────── Mutation tools ─────────────\n\n reg(\n \"form_set_value\",\n \"Set one field's value. The host's controlled state updates and the human sees the field change.\",\n {\n field: { type: \"string\" },\n value: { description: \"Value to set. Type depends on the field's `type`.\" },\n },\n [\"field\", \"value\"],\n async (args) => {\n const name = String(args.field ?? \"\");\n const fieldDef = adapter.getFields().find((f) => f.name === name);\n if (!fieldDef) return errorResult(`Unknown field: ${name}`);\n const done = await untilClosed(\n commit(() => adapter.setValue(name, args.value), `Host refused the write to \"${name}\".`),\n );\n if (done === CLOSED) return closedUnknown(`the write to \"${name}\"`);\n if (!done.ok) return errorResult(done.error);\n return textResult(`${name} ← ${JSON.stringify(args.value)}`, { field: name, value: args.value });\n },\n true,\n );\n\n reg(\n \"form_set_values\",\n \"Set multiple fields atomically. Pass a `values` object keyed by field name.\",\n { values: { type: \"object\" } },\n [\"values\"],\n async (args) => {\n const values = (args.values && typeof args.values === \"object\") ? args.values as Record<string, unknown> : {};\n const fields = adapter.getFields();\n const known = new Set(fields.map((f) => f.name));\n const unknownKeys = Object.keys(values).filter((k) => !known.has(k));\n if (unknownKeys.length) return errorResult(`Unknown fields: ${unknownKeys.join(\", \")}`);\n const setValues = adapter.setValues;\n if (setValues) {\n const done = await untilClosed(commit(() => setValues(values), \"Host refused the write.\"));\n if (done === CLOSED) return closedUnknown(\"the write\");\n if (!done.ok) return errorResult(done.error);\n } else {\n // No batch setter: one host commit per field, in order, stopping at the\n // first refusal. Earlier fields stay committed, so say exactly which.\n const keys = Object.keys(values);\n const committed: string[] = [];\n for (const [i, k] of keys.entries()) {\n const done = await untilClosed(\n commit(() => adapter.setValue(k, values[k]), `Host refused the write to \"${k}\".`),\n );\n if (done === CLOSED) {\n return {\n ...closedUnknown(`the write to \"${k}\"`),\n structuredContent: { committed, pending: k, notAttempted: keys.slice(i + 1) },\n };\n }\n if (!done.ok) {\n const state = { committed, failed: k, notAttempted: keys.slice(i + 1) };\n if (committed.length) {\n // Those writes DID commit; every committed mutation is observable,\n // and wrapToolWithActivity skips error results, so emit it here.\n emitActivity({\n agentId: agent.id,\n agentName: agent.name,\n agentColor: agent.color,\n target: target({}),\n action: \"form_set_values\",\n timestamp: Date.now(),\n meta: state,\n });\n }\n return {\n ...errorResult(\n `\"${k}\" was refused: ${done.error} ` +\n (committed.length\n ? `Already committed and NOT rolled back: ${committed.join(\", \")}. `\n : \"Nothing was committed. \") +\n (state.notAttempted.length ? `Not attempted: ${state.notAttempted.join(\", \")}.` : \"\"),\n ),\n structuredContent: state,\n };\n }\n committed.push(k);\n }\n }\n return textResult(`Set ${Object.keys(values).length} field(s)`, { values });\n },\n true,\n );\n\n reg(\n \"form_focus\",\n \"Move browser focus to a field (host-implemented). Useful before streaming text into it.\",\n { field: { type: \"string\" } },\n [\"field\"],\n (args) => {\n const name = String(args.field ?? \"\");\n if (!adapter.focus) return errorResult(\"Host did not provide a focus implementation.\");\n if (!adapter.getFields().find((f) => f.name === name)) {\n return errorResult(`Unknown field: ${name}`);\n }\n adapter.focus(name);\n return textResult(`Focused ${name}`, { field: name });\n },\n true,\n );\n\n reg(\n \"form_submit\",\n \"Submit the form. Host returns ok + values (or an error). In pendingMode this is staged for the human to confirm first.\",\n {},\n [],\n async () => {\n if (!adapter.submit) return errorResult(\"Host did not provide a submit implementation.\");\n // Trust-but-verify: a form submit is a human-visible / often-destructive\n // action, so gate it on a human confirm when one is wired.\n if (pendingMode && adapter.confirm) {\n const confirm = adapter.confirm;\n const ok = await untilClosed(Promise.resolve().then(() => confirm({\n action: \"submit\",\n form: formId,\n title: adapter.title,\n values: adapter.getValues(),\n })));\n // The form went away while the human was deciding: never submit a\n // form that is no longer on screen, whatever the late answer is.\n if (ok === CLOSED) {\n return errorResult(`Form \"${formId}\" was closed before the submit was confirmed. Nothing was submitted.`);\n }\n if (!ok) return errorResult(\"Declined by user\");\n }\n const submit = adapter.submit;\n const result = await untilClosed(Promise.resolve().then(() => submit()));\n if (result === CLOSED) return closedUnknown(\"the submit\");\n if (!result.ok) return errorResult(hostErrorMessage(result.error, \"Submit failed\"));\n return textResult(\"Submitted\", { values: result.values });\n },\n true,\n );\n\n return {\n id: bridgeId,\n title: adapter.title ?? formId,\n dispose: () => {\n disposed = true;\n for (const d of disposers) d();\n // Settle every call still waiting on the host; see untilClosed.\n for (const close of [...onDispose]) close();\n onDispose.clear();\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/bridges/host-commit.ts
|
|
2
|
+
function hostErrorMessage(e, fallback) {
|
|
3
|
+
let msg;
|
|
4
|
+
if (e instanceof Error) msg = e.message;
|
|
5
|
+
else if (typeof e === "string") msg = e;
|
|
6
|
+
else if (e && typeof e === "object" && typeof e.message === "string") {
|
|
7
|
+
msg = e.message;
|
|
8
|
+
}
|
|
9
|
+
const firstFrame = msg ? msg.search(/\n\s+at\s/) : -1;
|
|
10
|
+
if (msg && firstFrame >= 0) msg = msg.slice(0, firstFrame);
|
|
11
|
+
msg = msg?.trim();
|
|
12
|
+
return msg ? msg : fallback;
|
|
13
|
+
}
|
|
14
|
+
async function commit(run, fallback) {
|
|
15
|
+
try {
|
|
16
|
+
const r = await run();
|
|
17
|
+
if (r && typeof r === "object" && r.ok === false) {
|
|
18
|
+
return { ok: false, error: hostErrorMessage(r.error, fallback) };
|
|
19
|
+
}
|
|
20
|
+
return { ok: true };
|
|
21
|
+
} catch (e) {
|
|
22
|
+
return { ok: false, error: hostErrorMessage(e, fallback) };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { commit, hostErrorMessage };
|
|
27
|
+
//# sourceMappingURL=chunk-DPBAEWB5.js.map
|
|
28
|
+
//# sourceMappingURL=chunk-DPBAEWB5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bridges/host-commit.ts"],"names":[],"mappings":";AAqBO,SAAS,gBAAA,CAAiB,GAAY,QAAA,EAA0B;AACrE,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI,CAAA,YAAa,KAAA,EAAO,GAAA,GAAM,CAAA,CAAE,OAAA;AAAA,OAAA,IACvB,OAAO,CAAA,KAAM,QAAA,EAAU,GAAA,GAAM,CAAA;AAAA,OAAA,IAC7B,KAAK,OAAO,CAAA,KAAM,YAAY,OAAQ,CAAA,CAA4B,YAAY,QAAA,EAAU;AAC/F,IAAA,GAAA,GAAO,CAAA,CAA0B,OAAA;AAAA,EACnC;AACA,EAAA,MAAM,UAAA,GAAa,GAAA,GAAM,GAAA,CAAI,MAAA,CAAO,WAAW,CAAA,GAAI,EAAA;AACnD,EAAA,IAAI,OAAO,UAAA,IAAc,CAAA,QAAS,GAAA,CAAI,KAAA,CAAM,GAAG,UAAU,CAAA;AACzD,EAAA,GAAA,GAAM,KAAK,IAAA,EAAK;AAChB,EAAA,OAAO,MAAM,GAAA,GAAM,QAAA;AACrB;AAGA,eAAsB,MAAA,CACpB,KACA,QAAA,EACwB;AACxB,EAAA,IAAI;AACF,IAAA,MAAM,CAAA,GAAI,MAAM,GAAA,EAAI;AACpB,IAAA,IAAI,KAAK,OAAO,CAAA,KAAM,QAAA,IAAY,CAAA,CAAE,OAAO,KAAA,EAAO;AAChD,MAAA,OAAO,EAAE,IAAI,KAAA,EAAO,KAAA,EAAO,iBAAiB,CAAA,CAAE,KAAA,EAAO,QAAQ,CAAA,EAAE;AAAA,IACjE;AACA,IAAA,OAAO,EAAE,IAAI,IAAA,EAAK;AAAA,EACpB,SAAS,CAAA,EAAG;AACV,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,OAAO,gBAAA,CAAiB,CAAA,EAAG,QAAQ,CAAA,EAAE;AAAA,EAC3D;AACF","file":"chunk-DPBAEWB5.js","sourcesContent":["/**\n * Shared plumbing for bridges whose adapter mutators may commit asynchronously\n * (screens since 0.49.0, forms since 0.52.0). Internal: not an entry point.\n *\n * The contract: a mutator returns `void` (sync, unchanged behaviour) or a\n * Promise resolving to `void` or `{ ok: true } | { ok: false; error }`. The\n * bridge awaits it, and only an accepted commit counts as success.\n */\n\n/** What an async mutator may resolve to, to acknowledge or refuse a commit. */\nexport type HostCommitResult = { ok: true } | { ok: false; error: string; code?: string };\n\nexport type CommitOutcome = { ok: true } | { ok: false; error: string };\n\n/**\n * The agent-facing text for a host failure. Only the message is used — never\n * `stack`, which carries file paths — matching the other bridges. A message\n * that itself embeds stack frames is cut at the first frame. A non-Error\n * rejection (a plain object, a response) has no reliable message, so it gets\n * the fallback rather than \"[object Object]\".\n */\nexport function hostErrorMessage(e: unknown, fallback: string): string {\n let msg: string | undefined;\n if (e instanceof Error) msg = e.message;\n else if (typeof e === \"string\") msg = e;\n else if (e && typeof e === \"object\" && typeof (e as { message?: unknown }).message === \"string\") {\n msg = (e as { message: string }).message;\n }\n const firstFrame = msg ? msg.search(/\\n\\s+at\\s/) : -1;\n if (msg && firstFrame >= 0) msg = msg.slice(0, firstFrame);\n msg = msg?.trim();\n return msg ? msg : fallback;\n}\n\n/** Run one adapter commit, await it, and normalise every way it can fail. Never rejects. */\nexport async function commit(\n run: () => void | Promise<void | HostCommitResult>,\n fallback: string,\n): Promise<CommitOutcome> {\n try {\n const r = await run();\n if (r && typeof r === \"object\" && r.ok === false) {\n return { ok: false, error: hostErrorMessage(r.error, fallback) };\n }\n return { ok: true };\n } catch (e) {\n return { ok: false, error: hostErrorMessage(e, fallback) };\n }\n}\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -605,6 +605,34 @@ function serialize(entry) {
|
|
|
605
605
|
};
|
|
606
606
|
}
|
|
607
607
|
|
|
608
|
+
// src/bridges/forms.ts
|
|
609
|
+
init_registry();
|
|
610
|
+
|
|
611
|
+
// src/bridges/host-commit.ts
|
|
612
|
+
function hostErrorMessage(e, fallback) {
|
|
613
|
+
let msg;
|
|
614
|
+
if (e instanceof Error) msg = e.message;
|
|
615
|
+
else if (typeof e === "string") msg = e;
|
|
616
|
+
else if (e && typeof e === "object" && typeof e.message === "string") {
|
|
617
|
+
msg = e.message;
|
|
618
|
+
}
|
|
619
|
+
const firstFrame = msg ? msg.search(/\n\s+at\s/) : -1;
|
|
620
|
+
if (msg && firstFrame >= 0) msg = msg.slice(0, firstFrame);
|
|
621
|
+
msg = msg?.trim();
|
|
622
|
+
return msg ? msg : fallback;
|
|
623
|
+
}
|
|
624
|
+
async function commit(run, fallback) {
|
|
625
|
+
try {
|
|
626
|
+
const r = await run();
|
|
627
|
+
if (r && typeof r === "object" && r.ok === false) {
|
|
628
|
+
return { ok: false, error: hostErrorMessage(r.error, fallback) };
|
|
629
|
+
}
|
|
630
|
+
return { ok: true };
|
|
631
|
+
} catch (e) {
|
|
632
|
+
return { ok: false, error: hostErrorMessage(e, fallback) };
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
608
636
|
// src/bridges/forms.ts
|
|
609
637
|
var DEFAULT_AGENT = { id: "agent", name: "Agent", color: "#a855f7" };
|
|
610
638
|
function registerFormBridge(host, options) {
|
|
@@ -615,6 +643,33 @@ function registerFormBridge(host, options) {
|
|
|
615
643
|
const bridgeId = `form:${adapter.id}`;
|
|
616
644
|
ensureUndoToolsRegistered(host, { defaultAgentId: agent.id });
|
|
617
645
|
const formId = adapter.id;
|
|
646
|
+
let disposed = false;
|
|
647
|
+
const onDispose = /* @__PURE__ */ new Set();
|
|
648
|
+
const CLOSED = /* @__PURE__ */ Symbol("form-closed");
|
|
649
|
+
const untilClosed = (p) => {
|
|
650
|
+
if (disposed) {
|
|
651
|
+
p.catch(() => {
|
|
652
|
+
});
|
|
653
|
+
return Promise.resolve(CLOSED);
|
|
654
|
+
}
|
|
655
|
+
return new Promise((resolve, reject) => {
|
|
656
|
+
const close = () => resolve(CLOSED);
|
|
657
|
+
onDispose.add(close);
|
|
658
|
+
p.then(
|
|
659
|
+
(v) => {
|
|
660
|
+
onDispose.delete(close);
|
|
661
|
+
resolve(v);
|
|
662
|
+
},
|
|
663
|
+
(e) => {
|
|
664
|
+
onDispose.delete(close);
|
|
665
|
+
reject(e);
|
|
666
|
+
}
|
|
667
|
+
);
|
|
668
|
+
});
|
|
669
|
+
};
|
|
670
|
+
const closedUnknown = (what) => errorResult(
|
|
671
|
+
`Form "${formId}" was closed before the host confirmed ${what}, so whether it was applied is unknown. Do not assume it was. If the form is open again, read it back (form_get_values) before retrying.`
|
|
672
|
+
);
|
|
618
673
|
const target = (args) => ({
|
|
619
674
|
kind: "form",
|
|
620
675
|
screenId: adapter.screenId,
|
|
@@ -626,7 +681,7 @@ function registerFormBridge(host, options) {
|
|
|
626
681
|
try {
|
|
627
682
|
return await handler(args);
|
|
628
683
|
} catch (e) {
|
|
629
|
-
return errorResult(e
|
|
684
|
+
return errorResult(hostErrorMessage(e, `${name} failed.`));
|
|
630
685
|
}
|
|
631
686
|
};
|
|
632
687
|
const final = isMutation ? wrapToolWithActivity(wrapped, {
|
|
@@ -694,11 +749,15 @@ function registerFormBridge(host, options) {
|
|
|
694
749
|
value: { description: "Value to set. Type depends on the field's `type`." }
|
|
695
750
|
},
|
|
696
751
|
["field", "value"],
|
|
697
|
-
(args) => {
|
|
752
|
+
async (args) => {
|
|
698
753
|
const name = String(args.field ?? "");
|
|
699
754
|
const fieldDef = adapter.getFields().find((f) => f.name === name);
|
|
700
755
|
if (!fieldDef) return errorResult(`Unknown field: ${name}`);
|
|
701
|
-
|
|
756
|
+
const done = await untilClosed(
|
|
757
|
+
commit(() => adapter.setValue(name, args.value), `Host refused the write to "${name}".`)
|
|
758
|
+
);
|
|
759
|
+
if (done === CLOSED) return closedUnknown(`the write to "${name}"`);
|
|
760
|
+
if (!done.ok) return errorResult(done.error);
|
|
702
761
|
return textResult(`${name} \u2190 ${JSON.stringify(args.value)}`, { field: name, value: args.value });
|
|
703
762
|
},
|
|
704
763
|
true
|
|
@@ -708,16 +767,52 @@ function registerFormBridge(host, options) {
|
|
|
708
767
|
"Set multiple fields atomically. Pass a `values` object keyed by field name.",
|
|
709
768
|
{ values: { type: "object" } },
|
|
710
769
|
["values"],
|
|
711
|
-
(args) => {
|
|
770
|
+
async (args) => {
|
|
712
771
|
const values = args.values && typeof args.values === "object" ? args.values : {};
|
|
713
772
|
const fields = adapter.getFields();
|
|
714
773
|
const known = new Set(fields.map((f) => f.name));
|
|
715
774
|
const unknownKeys = Object.keys(values).filter((k) => !known.has(k));
|
|
716
775
|
if (unknownKeys.length) return errorResult(`Unknown fields: ${unknownKeys.join(", ")}`);
|
|
717
|
-
|
|
718
|
-
|
|
776
|
+
const setValues = adapter.setValues;
|
|
777
|
+
if (setValues) {
|
|
778
|
+
const done = await untilClosed(commit(() => setValues(values), "Host refused the write."));
|
|
779
|
+
if (done === CLOSED) return closedUnknown("the write");
|
|
780
|
+
if (!done.ok) return errorResult(done.error);
|
|
719
781
|
} else {
|
|
720
|
-
|
|
782
|
+
const keys = Object.keys(values);
|
|
783
|
+
const committed = [];
|
|
784
|
+
for (const [i, k] of keys.entries()) {
|
|
785
|
+
const done = await untilClosed(
|
|
786
|
+
commit(() => adapter.setValue(k, values[k]), `Host refused the write to "${k}".`)
|
|
787
|
+
);
|
|
788
|
+
if (done === CLOSED) {
|
|
789
|
+
return {
|
|
790
|
+
...closedUnknown(`the write to "${k}"`),
|
|
791
|
+
structuredContent: { committed, pending: k, notAttempted: keys.slice(i + 1) }
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
if (!done.ok) {
|
|
795
|
+
const state = { committed, failed: k, notAttempted: keys.slice(i + 1) };
|
|
796
|
+
if (committed.length) {
|
|
797
|
+
fancyAutoCommon.emitActivity({
|
|
798
|
+
agentId: agent.id,
|
|
799
|
+
agentName: agent.name,
|
|
800
|
+
agentColor: agent.color,
|
|
801
|
+
target: target({}),
|
|
802
|
+
action: "form_set_values",
|
|
803
|
+
timestamp: Date.now(),
|
|
804
|
+
meta: state
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
return {
|
|
808
|
+
...errorResult(
|
|
809
|
+
`"${k}" was refused: ${done.error} ` + (committed.length ? `Already committed and NOT rolled back: ${committed.join(", ")}. ` : "Nothing was committed. ") + (state.notAttempted.length ? `Not attempted: ${state.notAttempted.join(", ")}.` : "")
|
|
810
|
+
),
|
|
811
|
+
structuredContent: state
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
committed.push(k);
|
|
815
|
+
}
|
|
721
816
|
}
|
|
722
817
|
return textResult(`Set ${Object.keys(values).length} field(s)`, { values });
|
|
723
818
|
},
|
|
@@ -747,16 +842,22 @@ function registerFormBridge(host, options) {
|
|
|
747
842
|
async () => {
|
|
748
843
|
if (!adapter.submit) return errorResult("Host did not provide a submit implementation.");
|
|
749
844
|
if (pendingMode && adapter.confirm) {
|
|
750
|
-
const
|
|
845
|
+
const confirm = adapter.confirm;
|
|
846
|
+
const ok = await untilClosed(Promise.resolve().then(() => confirm({
|
|
751
847
|
action: "submit",
|
|
752
848
|
form: formId,
|
|
753
849
|
title: adapter.title,
|
|
754
850
|
values: adapter.getValues()
|
|
755
|
-
});
|
|
851
|
+
})));
|
|
852
|
+
if (ok === CLOSED) {
|
|
853
|
+
return errorResult(`Form "${formId}" was closed before the submit was confirmed. Nothing was submitted.`);
|
|
854
|
+
}
|
|
756
855
|
if (!ok) return errorResult("Declined by user");
|
|
757
856
|
}
|
|
758
|
-
const
|
|
759
|
-
|
|
857
|
+
const submit = adapter.submit;
|
|
858
|
+
const result = await untilClosed(Promise.resolve().then(() => submit()));
|
|
859
|
+
if (result === CLOSED) return closedUnknown("the submit");
|
|
860
|
+
if (!result.ok) return errorResult(hostErrorMessage(result.error, "Submit failed"));
|
|
760
861
|
return textResult("Submitted", { values: result.values });
|
|
761
862
|
},
|
|
762
863
|
true
|
|
@@ -765,7 +866,10 @@ function registerFormBridge(host, options) {
|
|
|
765
866
|
id: bridgeId,
|
|
766
867
|
title: adapter.title ?? formId,
|
|
767
868
|
dispose: () => {
|
|
869
|
+
disposed = true;
|
|
768
870
|
for (const d of disposers) d();
|
|
871
|
+
for (const close of [...onDispose]) close();
|
|
872
|
+
onDispose.clear();
|
|
769
873
|
}
|
|
770
874
|
};
|
|
771
875
|
}
|
|
@@ -1847,29 +1951,6 @@ function parseTriple(v) {
|
|
|
1847
1951
|
// src/bridges/screens.ts
|
|
1848
1952
|
init_registry();
|
|
1849
1953
|
var DEFAULT_AGENT7 = { id: "agent", name: "Agent", color: "#a855f7" };
|
|
1850
|
-
function hostErrorMessage(e, fallback) {
|
|
1851
|
-
let msg;
|
|
1852
|
-
if (e instanceof Error) msg = e.message;
|
|
1853
|
-
else if (typeof e === "string") msg = e;
|
|
1854
|
-
else if (e && typeof e === "object" && typeof e.message === "string") {
|
|
1855
|
-
msg = e.message;
|
|
1856
|
-
}
|
|
1857
|
-
const firstFrame = msg ? msg.search(/\n\s+at\s/) : -1;
|
|
1858
|
-
if (msg && firstFrame >= 0) msg = msg.slice(0, firstFrame);
|
|
1859
|
-
msg = msg?.trim();
|
|
1860
|
-
return msg ? msg : fallback;
|
|
1861
|
-
}
|
|
1862
|
-
async function commit(run, fallback) {
|
|
1863
|
-
try {
|
|
1864
|
-
const r = await run();
|
|
1865
|
-
if (r && typeof r === "object" && r.ok === false) {
|
|
1866
|
-
return { ok: false, error: hostErrorMessage(r.error, fallback) };
|
|
1867
|
-
}
|
|
1868
|
-
return { ok: true };
|
|
1869
|
-
} catch (e) {
|
|
1870
|
-
return { ok: false, error: hostErrorMessage(e, fallback) };
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
1954
|
function registerScreensBridge(host, options) {
|
|
1874
1955
|
const { adapter } = options;
|
|
1875
1956
|
const agent = { ...DEFAULT_AGENT7, ...options.agent ?? {} };
|