@sjhmars/happy-bridge 0.2.0 → 0.2.2
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/lib/client.js +8 -8
- package/lib/index.js +33 -43
- package/lib/types/bridge.d.ts +3 -3
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -17,21 +17,21 @@ window.__ModuleLoader__.load({
|
|
|
17
17
|
document.head.appendChild(tag);
|
|
18
18
|
}
|
|
19
19
|
var HappyBridgeCard_module_css_default = {
|
|
20
|
-
"
|
|
21
|
-
"name": "l9rBEq_name",
|
|
20
|
+
"row": "l9rBEq_row",
|
|
22
21
|
"qr": "l9rBEq_qr",
|
|
23
|
-
"header": "l9rBEq_header",
|
|
24
|
-
"body": "l9rBEq_body",
|
|
25
22
|
"headText": "l9rBEq_headText",
|
|
26
23
|
"status": "l9rBEq_status",
|
|
27
|
-
"card": "l9rBEq_card",
|
|
28
24
|
"description": "l9rBEq_description",
|
|
25
|
+
"name": "l9rBEq_name",
|
|
29
26
|
"label": "l9rBEq_label",
|
|
30
|
-
"
|
|
27
|
+
"button": "l9rBEq_button",
|
|
28
|
+
"header": "l9rBEq_header",
|
|
29
|
+
"card": "l9rBEq_card",
|
|
31
30
|
"checkbox": "l9rBEq_checkbox",
|
|
32
|
-
"row": "l9rBEq_row",
|
|
33
31
|
"chevron": "l9rBEq_chevron",
|
|
34
|
-
"
|
|
32
|
+
"body": "l9rBEq_body",
|
|
33
|
+
"select": "l9rBEq_select",
|
|
34
|
+
"error": "l9rBEq_error"
|
|
35
35
|
};
|
|
36
36
|
//#endregion
|
|
37
37
|
//#region src/client/HappyBridgeCard.tsx
|
package/lib/index.js
CHANGED
|
@@ -2847,6 +2847,10 @@ var HappyBridge = class {
|
|
|
2847
2847
|
if (status === "idle") this.drainNewEvents(link, agent);
|
|
2848
2848
|
}, { global: true });
|
|
2849
2849
|
this.ctx.on("session/event", (session, event) => {
|
|
2850
|
+
if (event.type === "model/selection") {
|
|
2851
|
+
this.onHostModelSelected(String(session.header.id), event.data);
|
|
2852
|
+
return;
|
|
2853
|
+
}
|
|
2850
2854
|
const link = this.links.get(session.header.id);
|
|
2851
2855
|
if (link !== void 0) {
|
|
2852
2856
|
this.onSessionEvent(link, event);
|
|
@@ -2857,15 +2861,6 @@ var HappyBridge = class {
|
|
|
2857
2861
|
this.ensureMirror(session.header.id).catch((error) => this.log(`镜像会话失败:${String(error)}`));
|
|
2858
2862
|
}, { global: true });
|
|
2859
2863
|
this.ctx.on("approval/request", (req, next) => this.onApproval(req, next), { prepend: true });
|
|
2860
|
-
this.ctx.inject(["apiProxy"], (inner) => {
|
|
2861
|
-
const proxy = inner.get("apiProxy");
|
|
2862
|
-
if (proxy === void 0) return;
|
|
2863
|
-
const original = proxy.sessions.selectModel;
|
|
2864
|
-
proxy.sessions.selectModel = (request) => this.afterHostSelect(request, original.call(proxy.sessions, request));
|
|
2865
|
-
inner.effect(() => () => {
|
|
2866
|
-
proxy.sessions.selectModel = original;
|
|
2867
|
-
}, "happy-bridge: restore sessions.selectModel");
|
|
2868
|
-
});
|
|
2869
2864
|
this.ctx.inject(["userQuestions"], (inner) => {
|
|
2870
2865
|
const questions = inner.userQuestions;
|
|
2871
2866
|
const original = questions.ask;
|
|
@@ -3719,27 +3714,20 @@ var HappyBridge = class {
|
|
|
3719
3714
|
this.syncHostSelection(agent, next);
|
|
3720
3715
|
}
|
|
3721
3716
|
/**
|
|
3722
|
-
* Write the web picker's Host selection (`
|
|
3723
|
-
* composer model seat reloads without a click on the computer.
|
|
3717
|
+
* Write the web picker's Host selection (`sessionController.selectModel`) so
|
|
3718
|
+
* the composer model seat reloads without a click on the computer.
|
|
3724
3719
|
*/
|
|
3725
3720
|
async syncHostSelection(agent, next) {
|
|
3726
|
-
const
|
|
3727
|
-
if (
|
|
3721
|
+
const controller = this.ctx.get("sessionController");
|
|
3722
|
+
if (controller === void 0) return;
|
|
3728
3723
|
this.hostSelectFromPhone += 1;
|
|
3729
3724
|
try {
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
model: next.model,
|
|
3736
|
-
...next.reasoningEffort === void 0 ? {} : { reasoningEffort: next.reasoningEffort }
|
|
3737
|
-
}
|
|
3725
|
+
await controller.selectModel({
|
|
3726
|
+
sessionId: agent.id,
|
|
3727
|
+
provider: next.provider,
|
|
3728
|
+
model: next.model,
|
|
3729
|
+
...next.reasoningEffort === void 0 ? {} : { reasoningEffort: next.reasoningEffort }
|
|
3738
3730
|
});
|
|
3739
|
-
if (!response.result.ok) {
|
|
3740
|
-
this.log(`电脑模型栏未同步:${response.result.error.message}`);
|
|
3741
|
-
return;
|
|
3742
|
-
}
|
|
3743
3731
|
const link = this.links.get(agent.id);
|
|
3744
3732
|
if (link !== void 0 && !link.parked) this.pushMetadata(link);
|
|
3745
3733
|
} catch (error) {
|
|
@@ -3752,26 +3740,21 @@ var HappyBridge = class {
|
|
|
3752
3740
|
* After the web picker (or any Host caller) lands a selection, publish it
|
|
3753
3741
|
* to Happy. Phone-originated calls set {@link hostSelectFromPhone} and push themselves.
|
|
3754
3742
|
*/
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
if (this.hostSelectFromPhone) return response;
|
|
3758
|
-
if (!response.result.ok) return response;
|
|
3759
|
-
const { sessionId, provider, model, reasoningEffort } = request.payload;
|
|
3760
|
-
const id = String(sessionId);
|
|
3743
|
+
onHostModelSelected(sessionId, selection) {
|
|
3744
|
+
if (this.hostSelectFromPhone) return;
|
|
3761
3745
|
const next = {
|
|
3762
|
-
provider,
|
|
3763
|
-
model,
|
|
3764
|
-
...reasoningEffort === void 0 ? {} : { reasoningEffort: ReasoningEffortId(reasoningEffort) }
|
|
3746
|
+
provider: selection.provider,
|
|
3747
|
+
model: selection.model,
|
|
3748
|
+
...selection.reasoningEffort === void 0 ? {} : { reasoningEffort: ReasoningEffortId(selection.reasoningEffort) }
|
|
3765
3749
|
};
|
|
3766
|
-
this.models.set(
|
|
3767
|
-
const link = this.links.get(
|
|
3750
|
+
this.models.set(sessionId, next);
|
|
3751
|
+
const link = this.links.get(sessionId);
|
|
3768
3752
|
const agent = link?.agent;
|
|
3769
3753
|
if (agent !== void 0) {
|
|
3770
|
-
const
|
|
3771
|
-
if (
|
|
3754
|
+
const current = this.selections.get(agent);
|
|
3755
|
+
if (current !== void 0) current.current = next;
|
|
3772
3756
|
}
|
|
3773
3757
|
if (link !== void 0 && !link.parked) this.pushMetadata(link);
|
|
3774
|
-
return response;
|
|
3775
3758
|
}
|
|
3776
3759
|
/**
|
|
3777
3760
|
* Put a concrete reasoningEffort on a phone-spawned / phone-woken agent
|
|
@@ -3964,6 +3947,8 @@ var HappyBridge = class {
|
|
|
3964
3947
|
if (link.alwaysAllow.has(req.toolName)) return "allowed-once";
|
|
3965
3948
|
if (!grantAtLeast(this.config.remoteGrant, "approve")) return next();
|
|
3966
3949
|
const id = req.callId;
|
|
3950
|
+
const controller = new AbortController();
|
|
3951
|
+
req.signal = req.signal === void 0 ? controller.signal : AbortSignal.any([req.signal, controller.signal]);
|
|
3967
3952
|
this.startTool(link, id, req.toolName, req.reason === void 0 ? {} : { reason: req.reason });
|
|
3968
3953
|
const phone = new Promise((resolve) => {
|
|
3969
3954
|
link.pendingHuman = {
|
|
@@ -3974,13 +3959,18 @@ var HappyBridge = class {
|
|
|
3974
3959
|
};
|
|
3975
3960
|
});
|
|
3976
3961
|
this.pushRequests(link);
|
|
3977
|
-
const
|
|
3978
|
-
src: "
|
|
3962
|
+
const web = next().then((outcome) => ({
|
|
3963
|
+
src: "web",
|
|
3979
3964
|
outcome
|
|
3980
|
-
})
|
|
3965
|
+
}), () => ({
|
|
3981
3966
|
src: "web",
|
|
3967
|
+
outcome: "cancelled"
|
|
3968
|
+
}));
|
|
3969
|
+
const winner = await Promise.race([phone.then((outcome) => ({
|
|
3970
|
+
src: "phone",
|
|
3982
3971
|
outcome
|
|
3983
|
-
}))]);
|
|
3972
|
+
})), web]);
|
|
3973
|
+
if (winner.src === "phone") controller.abort();
|
|
3984
3974
|
if (winner.src === "web" && link.pendingHuman?.kind === "approval" && link.pendingHuman.id === id) {
|
|
3985
3975
|
delete link.pendingHuman;
|
|
3986
3976
|
this.clearRequest(link, id, winner.outcome === "allowed-once" ? "approved" : "canceled");
|
package/lib/types/bridge.d.ts
CHANGED
|
@@ -169,15 +169,15 @@ export declare class HappyBridge {
|
|
|
169
169
|
/** Keep the last Host pick so Happy metadata can echo the web composer. */
|
|
170
170
|
private rememberModel;
|
|
171
171
|
/**
|
|
172
|
-
* Write the web picker's Host selection (`
|
|
173
|
-
* composer model seat reloads without a click on the computer.
|
|
172
|
+
* Write the web picker's Host selection (`sessionController.selectModel`) so
|
|
173
|
+
* the composer model seat reloads without a click on the computer.
|
|
174
174
|
*/
|
|
175
175
|
private syncHostSelection;
|
|
176
176
|
/**
|
|
177
177
|
* After the web picker (or any Host caller) lands a selection, publish it
|
|
178
178
|
* to Happy. Phone-originated calls set {@link hostSelectFromPhone} and push themselves.
|
|
179
179
|
*/
|
|
180
|
-
private
|
|
180
|
+
private onHostModelSelected;
|
|
181
181
|
/**
|
|
182
182
|
* Put a concrete reasoningEffort on a phone-spawned / phone-woken agent
|
|
183
183
|
* before the first LLM request, matching the effort Happy metadata advertises.
|
package/package.json
CHANGED