@particle-academy/agent-integrations 0.51.0 → 0.53.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 +78 -0
- package/README.md +100 -0
- package/dist/bridges/forms.d.cts +60 -8
- package/dist/bridges/forms.d.ts +60 -8
- package/dist/bridges-forms.cjs +293 -51
- 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-AET2R7DJ.js +391 -0
- package/dist/chunk-AET2R7DJ.js.map +1 -0
- package/dist/chunk-DPBAEWB5.js +28 -0
- package/dist/chunk-DPBAEWB5.js.map +1 -0
- package/dist/index.cjs +304 -82
- 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 +163 -0
- package/package.json +1 -1
- package/dist/chunk-CP3EHLZJ.js.map +0 -1
- package/dist/chunk-DNF3QVTW.js +0 -172
- package/dist/chunk-DNF3QVTW.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -605,28 +605,251 @@ 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
|
+
|
|
636
|
+
// src/bridges/multi-instance.ts
|
|
637
|
+
var routers = /* @__PURE__ */ new WeakMap();
|
|
638
|
+
var quoteAll = (ids) => ids.map((id) => `"${id}"`).join(", ");
|
|
639
|
+
function joinInstanceRouter(host, options, id, instance) {
|
|
640
|
+
if (typeof id !== "string" || id.trim() === "") {
|
|
641
|
+
throw new Error(
|
|
642
|
+
`A ${options.family} on a shared host needs a non-empty string id (got ${JSON.stringify(id)}); agents select it by that id through the "${options.argument}" argument.`
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
let byFamily = routers.get(host);
|
|
646
|
+
if (!byFamily) {
|
|
647
|
+
byFamily = /* @__PURE__ */ new Map();
|
|
648
|
+
routers.set(host, byFamily);
|
|
649
|
+
}
|
|
650
|
+
let state = byFamily.get(options.family);
|
|
651
|
+
if (!state) {
|
|
652
|
+
const instances = /* @__PURE__ */ new Map();
|
|
653
|
+
const router = {
|
|
654
|
+
ids: () => [...instances.keys()],
|
|
655
|
+
entries: () => [...instances.entries()],
|
|
656
|
+
resolve: (requested) => resolve(options, instances, requested)
|
|
657
|
+
};
|
|
658
|
+
const uninstall = options.install(router);
|
|
659
|
+
state = { instances, router, uninstall };
|
|
660
|
+
byFamily.set(options.family, state);
|
|
661
|
+
}
|
|
662
|
+
const current = state;
|
|
663
|
+
current.instances.set(id, instance);
|
|
664
|
+
let left = false;
|
|
665
|
+
const leave = () => {
|
|
666
|
+
if (left) return;
|
|
667
|
+
left = true;
|
|
668
|
+
if (current.instances.get(id) !== instance) return;
|
|
669
|
+
current.instances.delete(id);
|
|
670
|
+
if (current.instances.size === 0 && byFamily.get(options.family) === current) {
|
|
671
|
+
byFamily.delete(options.family);
|
|
672
|
+
current.uninstall();
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
return { router: current.router, leave };
|
|
676
|
+
}
|
|
677
|
+
function resolve(options, instances, requested) {
|
|
678
|
+
const ids = [...instances.keys()];
|
|
679
|
+
const { family, argument, listTool } = options;
|
|
680
|
+
if (requested === void 0 || requested === null || requested === "") {
|
|
681
|
+
if (ids.length === 1) return { ok: true, id: ids[0], instance: instances.get(ids[0]) };
|
|
682
|
+
if (ids.length === 0) return { ok: false, error: `No ${family} is mounted.` };
|
|
683
|
+
return {
|
|
684
|
+
ok: false,
|
|
685
|
+
error: `${ids.length} ${family}s are mounted, so pass "${argument}" with one of: ${quoteAll(ids)}. ${listTool} describes them.`
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
if (typeof requested !== "string") {
|
|
689
|
+
return { ok: false, error: `"${argument}" must be a ${family} id string. Mounted: ${quoteAll(ids)}.` };
|
|
690
|
+
}
|
|
691
|
+
if (!instances.has(requested)) {
|
|
692
|
+
return {
|
|
693
|
+
ok: false,
|
|
694
|
+
error: `No ${family} "${requested}" is mounted. ` + (ids.length ? `Mounted: ${quoteAll(ids)}. ` : "") + `${listTool} lists what is open.`
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
return { ok: true, id: requested, instance: instances.get(requested) };
|
|
698
|
+
}
|
|
699
|
+
|
|
608
700
|
// src/bridges/forms.ts
|
|
609
701
|
var DEFAULT_AGENT = { id: "agent", name: "Agent", color: "#a855f7" };
|
|
702
|
+
var FORM_TOOLS_OWNER = "form-bridge";
|
|
703
|
+
var FORM_ARG = {
|
|
704
|
+
type: "string",
|
|
705
|
+
description: "Id of the form to act on (see form_list). Optional while exactly one form is mounted; required when several are."
|
|
706
|
+
};
|
|
707
|
+
var FORM_TOOL_DEFS = [
|
|
708
|
+
{
|
|
709
|
+
name: "form_describe",
|
|
710
|
+
description: "Describe a form \u2014 fields, types, options, required flags. Call this first to know what's fillable.",
|
|
711
|
+
properties: {},
|
|
712
|
+
required: []
|
|
713
|
+
},
|
|
714
|
+
{ name: "form_get_value", description: "Read a single field's current value.", properties: { field: { type: "string" } }, required: ["field"] },
|
|
715
|
+
{ name: "form_get_values", description: "Read every field's current value as a JSON object.", properties: {}, required: [] },
|
|
716
|
+
{
|
|
717
|
+
name: "form_set_value",
|
|
718
|
+
description: "Set one field's value. The host's controlled state updates and the human sees the field change.",
|
|
719
|
+
properties: {
|
|
720
|
+
field: { type: "string" },
|
|
721
|
+
value: { description: "Value to set. Type depends on the field's `type`." }
|
|
722
|
+
},
|
|
723
|
+
required: ["field", "value"]
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
name: "form_set_values",
|
|
727
|
+
description: "Set multiple fields atomically. Pass a `values` object keyed by field name.",
|
|
728
|
+
properties: { values: { type: "object" } },
|
|
729
|
+
required: ["values"]
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
name: "form_focus",
|
|
733
|
+
description: "Move browser focus to a field (host-implemented). Useful before streaming text into it.",
|
|
734
|
+
properties: { field: { type: "string" } },
|
|
735
|
+
required: ["field"]
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
name: "form_submit",
|
|
739
|
+
description: "Submit the form. Host returns ok + values (or an error). In pendingMode this is staged for the human to confirm first.",
|
|
740
|
+
properties: {},
|
|
741
|
+
required: []
|
|
742
|
+
}
|
|
743
|
+
];
|
|
744
|
+
function installFormTools(host, router) {
|
|
745
|
+
const disposers = [];
|
|
746
|
+
try {
|
|
747
|
+
for (const def of FORM_TOOL_DEFS) {
|
|
748
|
+
disposers.push(
|
|
749
|
+
host.registerTool(
|
|
750
|
+
{
|
|
751
|
+
name: def.name,
|
|
752
|
+
description: def.description,
|
|
753
|
+
inputSchema: {
|
|
754
|
+
type: "object",
|
|
755
|
+
properties: { form: FORM_ARG, ...def.properties },
|
|
756
|
+
required: def.required,
|
|
757
|
+
additionalProperties: false
|
|
758
|
+
}
|
|
759
|
+
},
|
|
760
|
+
async (args) => {
|
|
761
|
+
const target = router.resolve(args?.form);
|
|
762
|
+
if (!target.ok) return errorResult(target.error);
|
|
763
|
+
return target.instance.handlers[def.name](args);
|
|
764
|
+
},
|
|
765
|
+
{ owner: FORM_TOOLS_OWNER }
|
|
766
|
+
)
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
disposers.push(
|
|
770
|
+
host.registerTool(
|
|
771
|
+
{
|
|
772
|
+
name: "form_list",
|
|
773
|
+
description: "List the forms mounted on this host \u2014 id, title, screen and fields. Pass an id as `form` to the other form tools when more than one is mounted.",
|
|
774
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
775
|
+
},
|
|
776
|
+
() => {
|
|
777
|
+
try {
|
|
778
|
+
const forms = router.entries().map(([id, { adapter }]) => ({
|
|
779
|
+
id,
|
|
780
|
+
title: adapter.title,
|
|
781
|
+
screenId: adapter.screenId,
|
|
782
|
+
fields: adapter.getFields()
|
|
783
|
+
}));
|
|
784
|
+
const text = forms.map((f) => `${f.id}${f.title ? ` \u2014 ${f.title}` : ""}: ${f.fields.map((x) => x.name).join(", ") || "(no fields)"}`).join("\n");
|
|
785
|
+
return textResult(text || "(no forms)", { forms });
|
|
786
|
+
} catch (e) {
|
|
787
|
+
return errorResult(hostErrorMessage(e, "form_list failed."));
|
|
788
|
+
}
|
|
789
|
+
},
|
|
790
|
+
{ owner: FORM_TOOLS_OWNER }
|
|
791
|
+
)
|
|
792
|
+
);
|
|
793
|
+
} catch (e) {
|
|
794
|
+
for (const d of disposers) d();
|
|
795
|
+
throw e;
|
|
796
|
+
}
|
|
797
|
+
return () => {
|
|
798
|
+
for (const d of disposers) d();
|
|
799
|
+
};
|
|
800
|
+
}
|
|
610
801
|
function registerFormBridge(host, options) {
|
|
611
802
|
const { adapter } = options;
|
|
612
803
|
const agent = { ...DEFAULT_AGENT, ...options.agent ?? {} };
|
|
613
804
|
const pendingMode = options.pendingMode ?? true;
|
|
614
|
-
const
|
|
615
|
-
|
|
805
|
+
const formId = adapter?.id == null ? "" : String(adapter.id);
|
|
806
|
+
if (formId.trim() === "") {
|
|
807
|
+
throw new Error(
|
|
808
|
+
"registerFormBridge: adapter.id must be a non-empty string. It is how agents pick this form (the `form` argument) when several forms share a host."
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
const bridgeId = `form:${formId}`;
|
|
616
812
|
ensureUndoToolsRegistered(host, { defaultAgentId: agent.id });
|
|
617
|
-
const
|
|
813
|
+
const handlers = {};
|
|
814
|
+
let disposed = false;
|
|
815
|
+
const onDispose = /* @__PURE__ */ new Set();
|
|
816
|
+
const CLOSED = /* @__PURE__ */ Symbol("form-closed");
|
|
817
|
+
const untilClosed = (p) => {
|
|
818
|
+
if (disposed) {
|
|
819
|
+
p.catch(() => {
|
|
820
|
+
});
|
|
821
|
+
return Promise.resolve(CLOSED);
|
|
822
|
+
}
|
|
823
|
+
return new Promise((resolve2, reject) => {
|
|
824
|
+
const close = () => resolve2(CLOSED);
|
|
825
|
+
onDispose.add(close);
|
|
826
|
+
p.then(
|
|
827
|
+
(v) => {
|
|
828
|
+
onDispose.delete(close);
|
|
829
|
+
resolve2(v);
|
|
830
|
+
},
|
|
831
|
+
(e) => {
|
|
832
|
+
onDispose.delete(close);
|
|
833
|
+
reject(e);
|
|
834
|
+
}
|
|
835
|
+
);
|
|
836
|
+
});
|
|
837
|
+
};
|
|
838
|
+
const closedUnknown = (what) => errorResult(
|
|
839
|
+
`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.`
|
|
840
|
+
);
|
|
618
841
|
const target = (args) => ({
|
|
619
842
|
kind: "form",
|
|
620
843
|
screenId: adapter.screenId,
|
|
621
844
|
elementId: typeof args?.field === "string" ? `${formId}:${args.field}` : formId,
|
|
622
845
|
label: typeof args?.field === "string" ? `${adapter.title ?? formId} \u2192 ${args.field}` : adapter.title ?? formId
|
|
623
846
|
});
|
|
624
|
-
const reg = (name,
|
|
847
|
+
const reg = (name, handler, isMutation) => {
|
|
625
848
|
const wrapped = async (args) => {
|
|
626
849
|
try {
|
|
627
850
|
return await handler(args);
|
|
628
851
|
} catch (e) {
|
|
629
|
-
return errorResult(e
|
|
852
|
+
return errorResult(hostErrorMessage(e, `${name} failed.`));
|
|
630
853
|
}
|
|
631
854
|
};
|
|
632
855
|
const final = isMutation ? wrapToolWithActivity(wrapped, {
|
|
@@ -636,23 +859,10 @@ function registerFormBridge(host, options) {
|
|
|
636
859
|
screenId: adapter.screenId,
|
|
637
860
|
resolveTarget: ({ args }) => target(args)
|
|
638
861
|
}) : wrapped;
|
|
639
|
-
|
|
640
|
-
host.registerTool(
|
|
641
|
-
{
|
|
642
|
-
name,
|
|
643
|
-
description,
|
|
644
|
-
inputSchema: { type: "object", properties, required, additionalProperties: false }
|
|
645
|
-
},
|
|
646
|
-
final,
|
|
647
|
-
{ owner: bridgeId }
|
|
648
|
-
)
|
|
649
|
-
);
|
|
862
|
+
handlers[name] = final;
|
|
650
863
|
};
|
|
651
864
|
reg(
|
|
652
865
|
"form_describe",
|
|
653
|
-
`Describe the form "${formId}" \u2014 fields, types, options, required flags. Call this first to know what's fillable.`,
|
|
654
|
-
{},
|
|
655
|
-
[],
|
|
656
866
|
() => {
|
|
657
867
|
const fields = adapter.getFields();
|
|
658
868
|
const text = fields.map((f) => `${f.name}${f.required ? "*" : ""} (${f.type})${f.label ? ` \u2014 ${f.label}` : ""}`).join("\n");
|
|
@@ -662,9 +872,6 @@ function registerFormBridge(host, options) {
|
|
|
662
872
|
);
|
|
663
873
|
reg(
|
|
664
874
|
"form_get_value",
|
|
665
|
-
"Read a single field's current value.",
|
|
666
|
-
{ field: { type: "string" } },
|
|
667
|
-
["field"],
|
|
668
875
|
(args) => {
|
|
669
876
|
const name = String(args.field ?? "");
|
|
670
877
|
if (!adapter.getFields().find((f) => f.name === name)) {
|
|
@@ -677,9 +884,6 @@ function registerFormBridge(host, options) {
|
|
|
677
884
|
);
|
|
678
885
|
reg(
|
|
679
886
|
"form_get_values",
|
|
680
|
-
"Read every field's current value as a JSON object.",
|
|
681
|
-
{},
|
|
682
|
-
[],
|
|
683
887
|
() => {
|
|
684
888
|
const values = adapter.getValues();
|
|
685
889
|
return textResult(JSON.stringify(values, null, 2), values);
|
|
@@ -688,36 +892,67 @@ function registerFormBridge(host, options) {
|
|
|
688
892
|
);
|
|
689
893
|
reg(
|
|
690
894
|
"form_set_value",
|
|
691
|
-
|
|
692
|
-
{
|
|
693
|
-
field: { type: "string" },
|
|
694
|
-
value: { description: "Value to set. Type depends on the field's `type`." }
|
|
695
|
-
},
|
|
696
|
-
["field", "value"],
|
|
697
|
-
(args) => {
|
|
895
|
+
async (args) => {
|
|
698
896
|
const name = String(args.field ?? "");
|
|
699
897
|
const fieldDef = adapter.getFields().find((f) => f.name === name);
|
|
700
898
|
if (!fieldDef) return errorResult(`Unknown field: ${name}`);
|
|
701
|
-
|
|
899
|
+
const done = await untilClosed(
|
|
900
|
+
commit(() => adapter.setValue(name, args.value), `Host refused the write to "${name}".`)
|
|
901
|
+
);
|
|
902
|
+
if (done === CLOSED) return closedUnknown(`the write to "${name}"`);
|
|
903
|
+
if (!done.ok) return errorResult(done.error);
|
|
702
904
|
return textResult(`${name} \u2190 ${JSON.stringify(args.value)}`, { field: name, value: args.value });
|
|
703
905
|
},
|
|
704
906
|
true
|
|
705
907
|
);
|
|
706
908
|
reg(
|
|
707
909
|
"form_set_values",
|
|
708
|
-
|
|
709
|
-
{ values: { type: "object" } },
|
|
710
|
-
["values"],
|
|
711
|
-
(args) => {
|
|
910
|
+
async (args) => {
|
|
712
911
|
const values = args.values && typeof args.values === "object" ? args.values : {};
|
|
713
912
|
const fields = adapter.getFields();
|
|
714
913
|
const known = new Set(fields.map((f) => f.name));
|
|
715
914
|
const unknownKeys = Object.keys(values).filter((k) => !known.has(k));
|
|
716
915
|
if (unknownKeys.length) return errorResult(`Unknown fields: ${unknownKeys.join(", ")}`);
|
|
717
|
-
|
|
718
|
-
|
|
916
|
+
const setValues = adapter.setValues;
|
|
917
|
+
if (setValues) {
|
|
918
|
+
const done = await untilClosed(commit(() => setValues(values), "Host refused the write."));
|
|
919
|
+
if (done === CLOSED) return closedUnknown("the write");
|
|
920
|
+
if (!done.ok) return errorResult(done.error);
|
|
719
921
|
} else {
|
|
720
|
-
|
|
922
|
+
const keys = Object.keys(values);
|
|
923
|
+
const committed = [];
|
|
924
|
+
for (const [i, k] of keys.entries()) {
|
|
925
|
+
const done = await untilClosed(
|
|
926
|
+
commit(() => adapter.setValue(k, values[k]), `Host refused the write to "${k}".`)
|
|
927
|
+
);
|
|
928
|
+
if (done === CLOSED) {
|
|
929
|
+
return {
|
|
930
|
+
...closedUnknown(`the write to "${k}"`),
|
|
931
|
+
structuredContent: { committed, pending: k, notAttempted: keys.slice(i + 1) }
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
if (!done.ok) {
|
|
935
|
+
const state = { committed, failed: k, notAttempted: keys.slice(i + 1) };
|
|
936
|
+
if (committed.length) {
|
|
937
|
+
fancyAutoCommon.emitActivity({
|
|
938
|
+
agentId: agent.id,
|
|
939
|
+
agentName: agent.name,
|
|
940
|
+
agentColor: agent.color,
|
|
941
|
+
target: target({}),
|
|
942
|
+
action: "form_set_values",
|
|
943
|
+
timestamp: Date.now(),
|
|
944
|
+
meta: state
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
return {
|
|
948
|
+
...errorResult(
|
|
949
|
+
`"${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(", ")}.` : "")
|
|
950
|
+
),
|
|
951
|
+
structuredContent: state
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
committed.push(k);
|
|
955
|
+
}
|
|
721
956
|
}
|
|
722
957
|
return textResult(`Set ${Object.keys(values).length} field(s)`, { values });
|
|
723
958
|
},
|
|
@@ -725,9 +960,6 @@ function registerFormBridge(host, options) {
|
|
|
725
960
|
);
|
|
726
961
|
reg(
|
|
727
962
|
"form_focus",
|
|
728
|
-
"Move browser focus to a field (host-implemented). Useful before streaming text into it.",
|
|
729
|
-
{ field: { type: "string" } },
|
|
730
|
-
["field"],
|
|
731
963
|
(args) => {
|
|
732
964
|
const name = String(args.field ?? "");
|
|
733
965
|
if (!adapter.focus) return errorResult("Host did not provide a focus implementation.");
|
|
@@ -741,31 +973,44 @@ function registerFormBridge(host, options) {
|
|
|
741
973
|
);
|
|
742
974
|
reg(
|
|
743
975
|
"form_submit",
|
|
744
|
-
"Submit the form. Host returns ok + values (or an error). In pendingMode this is staged for the human to confirm first.",
|
|
745
|
-
{},
|
|
746
|
-
[],
|
|
747
976
|
async () => {
|
|
748
977
|
if (!adapter.submit) return errorResult("Host did not provide a submit implementation.");
|
|
749
978
|
if (pendingMode && adapter.confirm) {
|
|
750
|
-
const
|
|
979
|
+
const confirm = adapter.confirm;
|
|
980
|
+
const ok = await untilClosed(Promise.resolve().then(() => confirm({
|
|
751
981
|
action: "submit",
|
|
752
982
|
form: formId,
|
|
753
983
|
title: adapter.title,
|
|
754
984
|
values: adapter.getValues()
|
|
755
|
-
});
|
|
985
|
+
})));
|
|
986
|
+
if (ok === CLOSED) {
|
|
987
|
+
return errorResult(`Form "${formId}" was closed before the submit was confirmed. Nothing was submitted.`);
|
|
988
|
+
}
|
|
756
989
|
if (!ok) return errorResult("Declined by user");
|
|
757
990
|
}
|
|
758
|
-
const
|
|
759
|
-
|
|
991
|
+
const submit = adapter.submit;
|
|
992
|
+
const result = await untilClosed(Promise.resolve().then(() => submit()));
|
|
993
|
+
if (result === CLOSED) return closedUnknown("the submit");
|
|
994
|
+
if (!result.ok) return errorResult(hostErrorMessage(result.error, "Submit failed"));
|
|
760
995
|
return textResult("Submitted", { values: result.values });
|
|
761
996
|
},
|
|
762
997
|
true
|
|
763
998
|
);
|
|
999
|
+
const membership = joinInstanceRouter(
|
|
1000
|
+
host,
|
|
1001
|
+
{ family: "form", argument: "form", listTool: "form_list", install: (router) => installFormTools(host, router) },
|
|
1002
|
+
formId,
|
|
1003
|
+
{ adapter, handlers }
|
|
1004
|
+
);
|
|
764
1005
|
return {
|
|
765
1006
|
id: bridgeId,
|
|
766
1007
|
title: adapter.title ?? formId,
|
|
767
1008
|
dispose: () => {
|
|
768
|
-
|
|
1009
|
+
if (disposed) return;
|
|
1010
|
+
disposed = true;
|
|
1011
|
+
membership.leave();
|
|
1012
|
+
for (const close of [...onDispose]) close();
|
|
1013
|
+
onDispose.clear();
|
|
769
1014
|
}
|
|
770
1015
|
};
|
|
771
1016
|
}
|
|
@@ -1847,29 +2092,6 @@ function parseTriple(v) {
|
|
|
1847
2092
|
// src/bridges/screens.ts
|
|
1848
2093
|
init_registry();
|
|
1849
2094
|
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
2095
|
function registerScreensBridge(host, options) {
|
|
1874
2096
|
const { adapter } = options;
|
|
1875
2097
|
const agent = { ...DEFAULT_AGENT7, ...options.agent ?? {} };
|
|
@@ -3230,7 +3452,7 @@ function registerTerminalBridge(host, options) {
|
|
|
3230
3452
|
const all = options.terminals ? options.terminals() : options.adapter ? [{ id: "terminal", label: "Terminal", active: true, ...options.adapter }] : [];
|
|
3231
3453
|
return options.canAccess ? all.filter((t) => options.canAccess(t.id)) : all;
|
|
3232
3454
|
};
|
|
3233
|
-
const
|
|
3455
|
+
const resolve2 = (id) => {
|
|
3234
3456
|
const list = listTerminals();
|
|
3235
3457
|
if (typeof id === "string" && id !== "") return list.find((t) => t.id === id);
|
|
3236
3458
|
return list.find((t) => t.active) ?? list[0];
|
|
@@ -3295,7 +3517,7 @@ function registerTerminalBridge(host, options) {
|
|
|
3295
3517
|
);
|
|
3296
3518
|
};
|
|
3297
3519
|
const need = (args) => {
|
|
3298
|
-
const t =
|
|
3520
|
+
const t = resolve2(args.terminal);
|
|
3299
3521
|
if (!t) {
|
|
3300
3522
|
const ids = listTerminals().map((x) => x.id).join(", ") || "(none)";
|
|
3301
3523
|
throw new Error(
|
|
@@ -3380,7 +3602,7 @@ function registerTerminalBridge(host, options) {
|
|
|
3380
3602
|
(args) => stageOrExec(need(args), "write", String(args.data)),
|
|
3381
3603
|
true,
|
|
3382
3604
|
// Skip the peer broadcast while only staged; emit once it actually executes.
|
|
3383
|
-
(args, result) => result?.structuredContent?.pending ? null : target(`write:${String(args.terminal ?? "")}`,
|
|
3605
|
+
(args, result) => result?.structuredContent?.pending ? null : target(`write:${String(args.terminal ?? "")}`, resolve2(args.terminal)?.id)
|
|
3384
3606
|
);
|
|
3385
3607
|
reg(
|
|
3386
3608
|
"terminal_run",
|
|
@@ -3389,7 +3611,7 @@ function registerTerminalBridge(host, options) {
|
|
|
3389
3611
|
["command"],
|
|
3390
3612
|
(args) => stageOrExec(need(args), "run", String(args.command)),
|
|
3391
3613
|
true,
|
|
3392
|
-
(args, result) => result?.structuredContent?.pending ? null : target(truncate(String(args.command ?? "")),
|
|
3614
|
+
(args, result) => result?.structuredContent?.pending ? null : target(truncate(String(args.command ?? "")), resolve2(args.terminal)?.id)
|
|
3393
3615
|
);
|
|
3394
3616
|
if (options.allowAgentConfirm) {
|
|
3395
3617
|
reg(
|
|
@@ -3401,7 +3623,7 @@ function registerTerminalBridge(host, options) {
|
|
|
3401
3623
|
const id = String(args.id);
|
|
3402
3624
|
const entry = staged.get(id);
|
|
3403
3625
|
if (!entry) return errorResult(`No staged command ${id}`);
|
|
3404
|
-
const t =
|
|
3626
|
+
const t = resolve2(entry.terminalId);
|
|
3405
3627
|
if (!t) return errorResult(`Terminal '${entry.terminalId}' is gone \u2014 cannot run ${id}`);
|
|
3406
3628
|
staged.delete(id);
|
|
3407
3629
|
await exec(t, entry.kind, entry.data);
|
|
@@ -3443,7 +3665,7 @@ function registerTerminalBridge(host, options) {
|
|
|
3443
3665
|
return textResult(`cleared ${t.id}`, { terminal: t.id });
|
|
3444
3666
|
},
|
|
3445
3667
|
true,
|
|
3446
|
-
(args) => target(`clear:${String(args.terminal ?? "")}`,
|
|
3668
|
+
(args) => target(`clear:${String(args.terminal ?? "")}`, resolve2(args.terminal)?.id)
|
|
3447
3669
|
);
|
|
3448
3670
|
}
|
|
3449
3671
|
if (canShells) {
|
|
@@ -3483,7 +3705,7 @@ function registerTerminalBridge(host, options) {
|
|
|
3483
3705
|
return textResult(`Switched ${t.id} shell to ${id}`, { shell: id, terminal: t.id });
|
|
3484
3706
|
},
|
|
3485
3707
|
true,
|
|
3486
|
-
(args) => target(`shell:${String(args.id ?? "")}`,
|
|
3708
|
+
(args) => target(`shell:${String(args.id ?? "")}`, resolve2(args.terminal)?.id)
|
|
3487
3709
|
);
|
|
3488
3710
|
}
|
|
3489
3711
|
return {
|
|
@@ -3497,7 +3719,7 @@ function registerTerminalBridge(host, options) {
|
|
|
3497
3719
|
confirm: (id) => {
|
|
3498
3720
|
const e = staged.get(id);
|
|
3499
3721
|
if (e) {
|
|
3500
|
-
const t =
|
|
3722
|
+
const t = resolve2(e.terminalId);
|
|
3501
3723
|
staged.delete(id);
|
|
3502
3724
|
if (t) void exec(t, e.kind, e.data);
|
|
3503
3725
|
}
|