@agimon-ai/po-voice 0.0.0-alpha.113 → 0.0.0-alpha.115
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/extensions/server.cjs +250 -46
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.mjs +250 -46
- package/dist/extensions/server.mjs.map +1 -1
- package/package.json +12 -11
|
@@ -13,6 +13,7 @@ node_fs = require_voiceWorkerProtocol.__toESM(node_fs, 1);
|
|
|
13
13
|
let node_path = require("node:path");
|
|
14
14
|
node_path = require_voiceWorkerProtocol.__toESM(node_path, 1);
|
|
15
15
|
let node_crypto = require("node:crypto");
|
|
16
|
+
let _agimon_ai_po_core_narration = require("@agimon-ai/po-core/narration");
|
|
16
17
|
let _agimon_ai_po_core_telemetry = require("@agimon-ai/po-core/telemetry");
|
|
17
18
|
let _agimon_ai_po_core_voiceTools = require("@agimon-ai/po-core/voiceTools");
|
|
18
19
|
let _agimon_ai_po_core_piSettings = require("@agimon-ai/po-core/piSettings");
|
|
@@ -20,12 +21,14 @@ let typebox_value = require("typebox/value");
|
|
|
20
21
|
let _agimon_ai_po_core_serverPlugin = require("@agimon-ai/po-core/serverPlugin");
|
|
21
22
|
let _agimon_ai_po_core_extensionFile = require("@agimon-ai/po-core/extensionFile");
|
|
22
23
|
let _agimon_ai_po_session = require("@agimon-ai/po-session");
|
|
24
|
+
let _agimon_ai_po_core_approval = require("@agimon-ai/po-core/approval");
|
|
23
25
|
let _agimon_ai_po_core_clientMetadata = require("@agimon-ai/po-core/clientMetadata");
|
|
26
|
+
let _agimon_ai_po_core_hubChannel = require("@agimon-ai/po-core/hubChannel");
|
|
27
|
+
let _agimon_ai_po_core_notification = require("@agimon-ai/po-core/notification");
|
|
28
|
+
let _agimon_ai_po_notification = require("@agimon-ai/po-notification");
|
|
24
29
|
let xstate = require("xstate");
|
|
25
30
|
let _agimon_ai_po_config = require("@agimon-ai/po-config");
|
|
26
31
|
let _agimon_ai_po_core_chatgptAuth = require("@agimon-ai/po-core/chatgptAuth");
|
|
27
|
-
let _agimon_ai_po_core_approval = require("@agimon-ai/po-core/approval");
|
|
28
|
-
let _agimon_ai_po_core_hubChannel = require("@agimon-ai/po-core/hubChannel");
|
|
29
32
|
let _agimon_ai_po_core_packageApi = require("@agimon-ai/po-core/packageApi");
|
|
30
33
|
let _agimon_ai_po_minor_mode = require("@agimon-ai/po-minor-mode");
|
|
31
34
|
let _agimon_ai_po_core_pluginProtocol = require("@agimon-ai/po-core/pluginProtocol");
|
|
@@ -3220,7 +3223,6 @@ var GlobalLiveCompanion = class {
|
|
|
3220
3223
|
spokenRequest = "";
|
|
3221
3224
|
approvalChallenges = /* @__PURE__ */ new Map();
|
|
3222
3225
|
attentionSubscriptions = /* @__PURE__ */ new Map();
|
|
3223
|
-
reminders = /* @__PURE__ */ new Map();
|
|
3224
3226
|
feedbackId;
|
|
3225
3227
|
feedbackRevision = 0;
|
|
3226
3228
|
clock = new require_voiceWorkerProtocol.SystemClock();
|
|
@@ -3269,7 +3271,6 @@ var GlobalLiveCompanion = class {
|
|
|
3269
3271
|
if (state === "disabled") {
|
|
3270
3272
|
this.lifecycle = {};
|
|
3271
3273
|
this.pendingActivation = void 0;
|
|
3272
|
-
this.clearReminders();
|
|
3273
3274
|
this.feedbackId = void 0;
|
|
3274
3275
|
this.approvalChallenges.clear();
|
|
3275
3276
|
this.approvalTurn = -1;
|
|
@@ -3494,7 +3495,6 @@ var GlobalLiveCompanion = class {
|
|
|
3494
3495
|
}
|
|
3495
3496
|
await this.live.announce("selected-feedback");
|
|
3496
3497
|
await this.live.announce(`attention:${targetSessionId}`);
|
|
3497
|
-
this.cancelReminder(targetSessionId);
|
|
3498
3498
|
await this.readFeedback(target);
|
|
3499
3499
|
for (const item of transfer.held) {
|
|
3500
3500
|
if (this.transfer !== transfer || this.stopped) return false;
|
|
@@ -3527,7 +3527,6 @@ var GlobalLiveCompanion = class {
|
|
|
3527
3527
|
this.pendingActivation = void 0;
|
|
3528
3528
|
if (this.transfer?.held.length) this.options.onNotice(`Explicit Voice stop discarded ${this.transfer.held.length} held requests for ${this.transfer.targetSessionId}.`);
|
|
3529
3529
|
const previousRoute = this.route;
|
|
3530
|
-
this.clearReminders();
|
|
3531
3530
|
this.feedbackId = void 0;
|
|
3532
3531
|
this.approvalChallenges.clear();
|
|
3533
3532
|
this.approvalTurn = -1;
|
|
@@ -3574,7 +3573,6 @@ var GlobalLiveCompanion = class {
|
|
|
3574
3573
|
if (this.selectedSessionId === sessionId) this.selectedSessionId = void 0;
|
|
3575
3574
|
this.approvals.delete(sessionId);
|
|
3576
3575
|
this.approvalChallenges.delete(sessionId);
|
|
3577
|
-
this.cancelReminder(sessionId);
|
|
3578
3576
|
if (this.route?.scope.sessionId === sessionId) this.stop().catch((error) => this.options.onNotice(`Voice route takeover failed: ${errorMessage(error)}`));
|
|
3579
3577
|
}
|
|
3580
3578
|
/** Tracks the person's own turns, never model-supplied text, for single-use approval challenges. */
|
|
@@ -3804,16 +3802,18 @@ var GlobalLiveCompanion = class {
|
|
|
3804
3802
|
this.fences.delete(sessionId);
|
|
3805
3803
|
this.approvals.delete(sessionId);
|
|
3806
3804
|
this.approvalChallenges.delete(sessionId);
|
|
3807
|
-
this.cancelReminder(sessionId);
|
|
3808
3805
|
this.announce(`attention:${sessionId}`);
|
|
3809
3806
|
this.announce(`approval:${sessionId}`);
|
|
3810
3807
|
}
|
|
3808
|
+
get announcementActivation() {
|
|
3809
|
+
return this.live.state === "active" ? this.activationId : void 0;
|
|
3810
|
+
}
|
|
3811
3811
|
announce(key, text) {
|
|
3812
3812
|
this.live.announce(key, text).catch((error) => this.options.onNotice(`Voice announcement failed: ${errorMessage(error)}`));
|
|
3813
3813
|
}
|
|
3814
3814
|
/** A pending approval came or went: offer a spoken answer for `ask`, point to the app for `human`. */
|
|
3815
3815
|
updateApproval(scope, approval, previous) {
|
|
3816
|
-
if (
|
|
3816
|
+
if (scope.sessionId !== this.selectedSessionId && scope.sessionId !== this.route?.scope.sessionId) return;
|
|
3817
3817
|
if (approval?.id === previous?.id) return;
|
|
3818
3818
|
this.approvalChallenges.delete(scope.sessionId);
|
|
3819
3819
|
this.announce(`approval:${scope.sessionId}`);
|
|
@@ -3838,11 +3838,8 @@ var GlobalLiveCompanion = class {
|
|
|
3838
3838
|
this.updateApproval(scope, approval);
|
|
3839
3839
|
}
|
|
3840
3840
|
updateAttention(scope, next, previous) {
|
|
3841
|
-
if (
|
|
3842
|
-
if (next.dialog?.id !== previous?.dialog?.id) {
|
|
3843
|
-
this.cancelReminder(scope.sessionId);
|
|
3844
|
-
this.announce(`attention:${scope.sessionId}`);
|
|
3845
|
-
}
|
|
3841
|
+
if (scope.sessionId !== this.selectedSessionId && scope.sessionId !== this.route?.scope.sessionId) return;
|
|
3842
|
+
if (next.dialog?.id !== previous?.dialog?.id) this.announce(`attention:${scope.sessionId}`);
|
|
3846
3843
|
if (this.live.state !== "active") return;
|
|
3847
3844
|
if (this.route?.scope.sessionId === scope.sessionId) {
|
|
3848
3845
|
if (next.dialog?.id !== previous?.dialog?.id) {
|
|
@@ -3853,27 +3850,6 @@ var GlobalLiveCompanion = class {
|
|
|
3853
3850
|
if (next.dialog?.purpose === "feedback" && next.dialog.id !== previous?.dialog?.id) this.readFeedback(this.route).catch((error) => this.options.onNotice(errorMessage(error)));
|
|
3854
3851
|
return;
|
|
3855
3852
|
}
|
|
3856
|
-
const name = next.name || scope.sessionId;
|
|
3857
|
-
const dialog = next.dialog;
|
|
3858
|
-
if (dialog && dialog.id !== previous?.dialog?.id) {
|
|
3859
|
-
const text = dialog.purpose === "feedback" ? `${name} asks: ${dialog.title.slice(0, 200)}. Switch there?` : `${name} is waiting for approval in the app.`;
|
|
3860
|
-
this.announce(`attention:${scope.sessionId}`, text);
|
|
3861
|
-
if (dialog.purpose === "feedback") {
|
|
3862
|
-
const timer = setTimeout(() => {
|
|
3863
|
-
this.reminders.delete(scope.sessionId);
|
|
3864
|
-
if (this.attention.get(scope.sessionId)?.dialog?.id === dialog.id && this.route?.scope.sessionId !== scope.sessionId && this.live.state === "active") this.announce(`attention:${scope.sessionId}`, `Reminder: ${text}`);
|
|
3865
|
-
}, 3e5);
|
|
3866
|
-
this.reminders.set(scope.sessionId, timer);
|
|
3867
|
-
}
|
|
3868
|
-
} else if (previous?.phase === "turn" && next.phase === "idle" && !dialog) {
|
|
3869
|
-
const activation = this.activationId;
|
|
3870
|
-
const fence = this.fences.get(scope.sessionId);
|
|
3871
|
-
this.agentRequest({ scope }, "/live/agent/outcome", "GET").then((outcome) => {
|
|
3872
|
-
if (this.attention.get(scope.sessionId) !== next || this.activationId !== activation || this.fences.get(scope.sessionId) !== fence || this.route?.scope.sessionId === scope.sessionId || this.live.state !== "active") return;
|
|
3873
|
-
const text = object(outcome)?.text;
|
|
3874
|
-
this.announce(`attention:${scope.sessionId}`, `${name} finished: ${typeof text === "string" && text.trim() ? text : "No final text outcome was reported."}`);
|
|
3875
|
-
}).catch((error) => this.options.onNotice(`Voice outcome unavailable: ${errorMessage(error)}`));
|
|
3876
|
-
}
|
|
3877
3853
|
}
|
|
3878
3854
|
resumeAttention() {
|
|
3879
3855
|
for (const scope of this.hub?.sessions() ?? []) {
|
|
@@ -3881,19 +3857,10 @@ var GlobalLiveCompanion = class {
|
|
|
3881
3857
|
if (next?.dialog) this.updateAttention(scope, next);
|
|
3882
3858
|
}
|
|
3883
3859
|
}
|
|
3884
|
-
cancelReminder(sessionId) {
|
|
3885
|
-
const timer = this.reminders.get(sessionId);
|
|
3886
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
3887
|
-
this.reminders.delete(sessionId);
|
|
3888
|
-
}
|
|
3889
|
-
clearReminders() {
|
|
3890
|
-
for (const id of this.reminders.keys()) this.cancelReminder(id);
|
|
3891
|
-
}
|
|
3892
3860
|
clearAttention() {
|
|
3893
3861
|
for (const stop of this.attentionSubscriptions.values()) stop();
|
|
3894
3862
|
this.attentionSubscriptions.clear();
|
|
3895
3863
|
this.attention.clear();
|
|
3896
|
-
this.clearReminders();
|
|
3897
3864
|
}
|
|
3898
3865
|
async readFeedback(route) {
|
|
3899
3866
|
const revision = ++this.feedbackRevision;
|
|
@@ -3906,7 +3873,6 @@ var GlobalLiveCompanion = class {
|
|
|
3906
3873
|
}
|
|
3907
3874
|
if (this.feedbackId === feedback.id) return;
|
|
3908
3875
|
this.feedbackId = feedback.id;
|
|
3909
|
-
this.cancelReminder(route.scope.sessionId);
|
|
3910
3876
|
await this.live.announce(`attention:${route.scope.sessionId}`);
|
|
3911
3877
|
const question = JSON.stringify(feedback);
|
|
3912
3878
|
if (Buffer.byteLength(question, "utf8") > 12288) {
|
|
@@ -4121,6 +4087,7 @@ var GlobalLiveManager = class GlobalLiveManager {
|
|
|
4121
4087
|
host;
|
|
4122
4088
|
closed = false;
|
|
4123
4089
|
cleanupTimer;
|
|
4090
|
+
notices = /* @__PURE__ */ new Map();
|
|
4124
4091
|
scheduleCleanup() {
|
|
4125
4092
|
if (this.closed || this.cleanupTimer !== void 0 || this.channels.size === 0) return;
|
|
4126
4093
|
this.cleanupTimer = setTimeout(() => {
|
|
@@ -4146,6 +4113,209 @@ var GlobalLiveManager = class GlobalLiveManager {
|
|
|
4146
4113
|
static create(options) {
|
|
4147
4114
|
return Promise.resolve(new GlobalLiveManager(options));
|
|
4148
4115
|
}
|
|
4116
|
+
noticeTarget(deviceId, source) {
|
|
4117
|
+
if (this.closed || source.scope.parentSessionId !== void 0 || source.scope.sessionContext?.parentSessionId !== void 0 || source.attention?.parentSessionId !== void 0 || source.scope.sessionId.startsWith("peer/")) return;
|
|
4118
|
+
const sourceOwner = this.options.sessionOwners.get(source.scope.sessionId);
|
|
4119
|
+
if (sourceOwner && sourceOwner.deviceId !== deviceId) return;
|
|
4120
|
+
const companion = this.channels.get(deviceId)?.companion;
|
|
4121
|
+
const status = companion?.status;
|
|
4122
|
+
const activationId = companion?.announcementActivation;
|
|
4123
|
+
const coordinator = this.coordinators.get(deviceId);
|
|
4124
|
+
const sessionId = activationId ? status?.activeSessionId : !status || status.state === "disabled" ? coordinator?.activeSessionId() : void 0;
|
|
4125
|
+
if (!sessionId || sessionId === source.scope.sessionId || sessionId.startsWith("peer/")) return;
|
|
4126
|
+
const owner = this.options.sessionOwners.get(sessionId);
|
|
4127
|
+
if (owner?.deviceId !== deviceId) return;
|
|
4128
|
+
const endpoint = this.agents.get(sessionId)?.endpoint;
|
|
4129
|
+
const registration = coordinator?.registration(sessionId);
|
|
4130
|
+
if (!activationId && (!endpoint?.narrate || !registration?.active || registration.deviceId !== deviceId)) return;
|
|
4131
|
+
return {
|
|
4132
|
+
deviceId,
|
|
4133
|
+
sessionId,
|
|
4134
|
+
generation: owner.generation,
|
|
4135
|
+
activationId,
|
|
4136
|
+
...activationId ? {} : {
|
|
4137
|
+
endpoint,
|
|
4138
|
+
leaseId: registration?.leaseId
|
|
4139
|
+
}
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
4142
|
+
noticeTargets(source) {
|
|
4143
|
+
return [.../* @__PURE__ */ new Set([...this.channels.keys(), ...this.coordinators.keys()])].flatMap((deviceId) => {
|
|
4144
|
+
const target = this.noticeTarget(deviceId, source);
|
|
4145
|
+
return target ? [target] : [];
|
|
4146
|
+
});
|
|
4147
|
+
}
|
|
4148
|
+
currentTarget(source, target) {
|
|
4149
|
+
if (this.notices.get(source.scope.sessionId) !== source) return false;
|
|
4150
|
+
const current = this.noticeTarget(target.deviceId, source);
|
|
4151
|
+
return current?.sessionId === target.sessionId && current.generation === target.generation && current.activationId === target.activationId && current.endpoint === target.endpoint && current.leaseId === target.leaseId;
|
|
4152
|
+
}
|
|
4153
|
+
clearNotice(source) {
|
|
4154
|
+
for (const { target, controller } of source.pending.values()) {
|
|
4155
|
+
controller.abort();
|
|
4156
|
+
if (target.activationId === this.channels.get(target.deviceId)?.companion.announcementActivation) this.channels.get(target.deviceId)?.companion.announce(`notice:${source.scope.sessionId}`);
|
|
4157
|
+
}
|
|
4158
|
+
source.pending.clear();
|
|
4159
|
+
}
|
|
4160
|
+
pruneNotices() {
|
|
4161
|
+
for (const source of this.notices.values()) for (const [deviceId, pending] of source.pending) {
|
|
4162
|
+
if (this.currentTarget(source, pending.target)) continue;
|
|
4163
|
+
pending.controller.abort();
|
|
4164
|
+
this.channels.get(deviceId)?.companion.announce(`notice:${source.scope.sessionId}`);
|
|
4165
|
+
source.pending.delete(deviceId);
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
sendNotice(source, text, targets = this.noticeTargets(source)) {
|
|
4169
|
+
this.clearNotice(source);
|
|
4170
|
+
const request = (0, _agimon_ai_po_core_narration.createNarrationRequest)(text.replace(/([.!?])\.$/u, "$1"));
|
|
4171
|
+
if (!request) return;
|
|
4172
|
+
for (const target of targets) {
|
|
4173
|
+
if (!this.currentTarget(source, target)) continue;
|
|
4174
|
+
const controller = new AbortController();
|
|
4175
|
+
const pending = {
|
|
4176
|
+
target,
|
|
4177
|
+
controller
|
|
4178
|
+
};
|
|
4179
|
+
source.pending.set(target.deviceId, pending);
|
|
4180
|
+
Promise.resolve().then(async () => {
|
|
4181
|
+
if (controller.signal.aborted || !this.currentTarget(source, target)) return;
|
|
4182
|
+
if (target.activationId) {
|
|
4183
|
+
this.channels.get(target.deviceId)?.companion.announce(`notice:${source.scope.sessionId}`, request.text);
|
|
4184
|
+
return;
|
|
4185
|
+
}
|
|
4186
|
+
const timer = setInterval(() => {
|
|
4187
|
+
if (!this.currentTarget(source, target)) controller.abort();
|
|
4188
|
+
}, 250);
|
|
4189
|
+
timer.unref?.();
|
|
4190
|
+
const stopChecking = () => clearInterval(timer);
|
|
4191
|
+
controller.signal.addEventListener("abort", stopChecking, { once: true });
|
|
4192
|
+
try {
|
|
4193
|
+
await target.endpoint?.narrate?.(request.text, target.deviceId, controller.signal);
|
|
4194
|
+
} finally {
|
|
4195
|
+
clearInterval(timer);
|
|
4196
|
+
controller.signal.removeEventListener("abort", stopChecking);
|
|
4197
|
+
if (source.pending.get(target.deviceId) === pending) source.pending.delete(target.deviceId);
|
|
4198
|
+
}
|
|
4199
|
+
}).catch((error) => {
|
|
4200
|
+
if (!controller.signal.aborted) this.options.companion.onNotice(`Voice notice failed: ${String(error)}`);
|
|
4201
|
+
});
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
inputNotice(source) {
|
|
4205
|
+
const dialog = source.attention?.dialog;
|
|
4206
|
+
const approval = source.approval ?? dialog?.approval;
|
|
4207
|
+
const inputId = approval?.id ?? dialog?.id;
|
|
4208
|
+
if (source.inputId === inputId) return;
|
|
4209
|
+
source.inputId = inputId;
|
|
4210
|
+
this.clearNotice(source);
|
|
4211
|
+
if (!inputId) return;
|
|
4212
|
+
if (source.timer !== void 0) clearTimeout(source.timer);
|
|
4213
|
+
source.timer = void 0;
|
|
4214
|
+
const title = approval ? "approval in the app" : dialog.title.slice(0, 200);
|
|
4215
|
+
this.sendNotice(source, `${(source.attention?.name || source.scope.name || source.scope.sessionId).slice(0, 200)} needs your input: ${title}.`);
|
|
4216
|
+
}
|
|
4217
|
+
finishLater(source, targets, delayMs) {
|
|
4218
|
+
const run = source.run;
|
|
4219
|
+
source.timer = setTimeout(() => {
|
|
4220
|
+
source.timer = void 0;
|
|
4221
|
+
const current = () => this.notices.get(source.scope.sessionId) === source && source.run === run && source.attention?.phase === "idle" && !source.inputId;
|
|
4222
|
+
(async () => {
|
|
4223
|
+
if (!current() || !targets.some((target) => this.currentTarget(source, target))) return;
|
|
4224
|
+
try {
|
|
4225
|
+
const state = await this.host?.sessionService?.readExecutionState?.(source.scope.sessionId);
|
|
4226
|
+
if (!current()) return;
|
|
4227
|
+
const decision = (0, _agimon_ai_po_notification.decideSessionFinished)(state, source.holdingSince, Date.now());
|
|
4228
|
+
if (decision.action === "finished") this.sendNotice(source, `${(source.attention?.name || source.scope.name || source.scope.sessionId).slice(0, 200)} finished.`, targets);
|
|
4229
|
+
else if (decision.action === "hold" || decision.action === "recheck") {
|
|
4230
|
+
if (decision.action === "hold") source.holdingSince = decision.holdingSince;
|
|
4231
|
+
this.finishLater(source, targets, decision.delayMs);
|
|
4232
|
+
} else if (decision.action === "unavailable") throw new Error("Execution state unavailable");
|
|
4233
|
+
} catch {
|
|
4234
|
+
if (current() && !source.warned) {
|
|
4235
|
+
source.warned = true;
|
|
4236
|
+
this.options.companion.onNotice("Voice completion check unavailable.");
|
|
4237
|
+
}
|
|
4238
|
+
}
|
|
4239
|
+
})();
|
|
4240
|
+
}, delayMs);
|
|
4241
|
+
source.timer.unref?.();
|
|
4242
|
+
}
|
|
4243
|
+
watchNotices(scope) {
|
|
4244
|
+
const host = this.host;
|
|
4245
|
+
if (!host || this.notices.has(scope.sessionId)) return;
|
|
4246
|
+
const source = {
|
|
4247
|
+
scope,
|
|
4248
|
+
stop() {},
|
|
4249
|
+
running: false,
|
|
4250
|
+
run: 0,
|
|
4251
|
+
entries: /* @__PURE__ */ new Set(),
|
|
4252
|
+
pending: /* @__PURE__ */ new Map()
|
|
4253
|
+
};
|
|
4254
|
+
this.notices.set(scope.sessionId, source);
|
|
4255
|
+
let seeding = true;
|
|
4256
|
+
const stopAttention = host.directEvents.subscribe(_agimon_ai_po_core_hubChannel.PO_SESSION_ATTENTION_EVENT, scope.sessionId, (payload) => {
|
|
4257
|
+
const next = payload;
|
|
4258
|
+
if (!next || ![
|
|
4259
|
+
"idle",
|
|
4260
|
+
"turn",
|
|
4261
|
+
"retry",
|
|
4262
|
+
"compaction"
|
|
4263
|
+
].includes(next.phase) || typeof next.awaitingInput !== "boolean" || next.name !== void 0 && typeof next.name !== "string" || next.parentSessionId !== void 0 && typeof next.parentSessionId !== "string" || next.dialog !== void 0 && (!next.dialog || typeof next.dialog.id !== "string" || typeof next.dialog.title !== "string")) return;
|
|
4264
|
+
source.attention = next;
|
|
4265
|
+
if (seeding) {
|
|
4266
|
+
source.running = next.phase !== "idle";
|
|
4267
|
+
source.inputId = next.dialog?.approval?.id ?? next.dialog?.id;
|
|
4268
|
+
return;
|
|
4269
|
+
}
|
|
4270
|
+
if (next.phase !== "idle" && !source.running) {
|
|
4271
|
+
source.running = true;
|
|
4272
|
+
source.run += 1;
|
|
4273
|
+
source.holdingSince = void 0;
|
|
4274
|
+
source.entries.clear();
|
|
4275
|
+
if (source.timer !== void 0) clearTimeout(source.timer);
|
|
4276
|
+
source.timer = void 0;
|
|
4277
|
+
this.clearNotice(source);
|
|
4278
|
+
}
|
|
4279
|
+
this.inputNotice(source);
|
|
4280
|
+
if (next.phase === "idle" && source.running) {
|
|
4281
|
+
source.running = false;
|
|
4282
|
+
if (!source.inputId) this.finishLater(source, this.noticeTargets(source), _agimon_ai_po_notification.SESSION_FINISHED_COOLDOWN_MS);
|
|
4283
|
+
}
|
|
4284
|
+
}, { replayLatest: true });
|
|
4285
|
+
const stopApproval = host.directEvents.subscribe(_agimon_ai_po_core_approval.PO_APPROVAL_VOICE_EVENT, scope.sessionId, (payload) => {
|
|
4286
|
+
source.approval = (0, _agimon_ai_po_core_approval.parsePoApprovalPrompt)(payload);
|
|
4287
|
+
if (seeding) source.inputId = source.approval?.id ?? source.inputId;
|
|
4288
|
+
else this.inputNotice(source);
|
|
4289
|
+
}, { replayLatest: true });
|
|
4290
|
+
seeding = false;
|
|
4291
|
+
const stopEntries = host.directEvents.subscribe(_agimon_ai_po_core_hubChannel.PO_SESSION_NOTIFICATION_EVENT, scope.sessionId, (payload) => {
|
|
4292
|
+
const entry = payload;
|
|
4293
|
+
if (!entry || typeof entry.entryId !== "string" || !entry.entryId || !(0, _agimon_ai_po_core_notification.isPoNotificationEntryData)(entry.data) || !entry.data.attention || source.entries.has(entry.entryId)) return;
|
|
4294
|
+
source.entries.add(entry.entryId);
|
|
4295
|
+
if (source.entries.size > 256) source.entries.delete(source.entries.values().next().value);
|
|
4296
|
+
const targets = this.noticeTargets(source);
|
|
4297
|
+
const run = source.run;
|
|
4298
|
+
queueMicrotask(() => {
|
|
4299
|
+
if (this.notices.get(scope.sessionId) !== source || source.run !== run) return;
|
|
4300
|
+
const approval = source.approval ?? source.attention?.dialog?.approval;
|
|
4301
|
+
if (approval && entry.data.title === "Approval needed" && entry.data.body === approval.title) return;
|
|
4302
|
+
this.sendNotice(source, `${(source.attention?.name || scope.name || scope.sessionId).slice(0, 200)} needs your input: ${(entry.data.title || entry.data.body).slice(0, 200)}.`, targets);
|
|
4303
|
+
});
|
|
4304
|
+
});
|
|
4305
|
+
source.stop = () => {
|
|
4306
|
+
stopAttention();
|
|
4307
|
+
stopApproval();
|
|
4308
|
+
stopEntries();
|
|
4309
|
+
};
|
|
4310
|
+
}
|
|
4311
|
+
removeNotices(sessionId) {
|
|
4312
|
+
const source = this.notices.get(sessionId);
|
|
4313
|
+
if (!source) return;
|
|
4314
|
+
source.stop();
|
|
4315
|
+
if (source.timer !== void 0) clearTimeout(source.timer);
|
|
4316
|
+
this.clearNotice(source);
|
|
4317
|
+
this.notices.delete(sessionId);
|
|
4318
|
+
}
|
|
4149
4319
|
reserve(deviceId, sessionId, takeoverGeneration) {
|
|
4150
4320
|
const identity = this.channels.get(deviceId)?.broker.owner;
|
|
4151
4321
|
if (!identity) throw new Error("Connect this device media before reserving a work session.");
|
|
@@ -4155,6 +4325,7 @@ var GlobalLiveManager = class GlobalLiveManager {
|
|
|
4155
4325
|
if (reservations.get(sessionId) !== holder) return;
|
|
4156
4326
|
reservations.delete(sessionId);
|
|
4157
4327
|
this.channels.get(deviceId)?.companion.fenceWorkSession(sessionId);
|
|
4328
|
+
this.pruneNotices();
|
|
4158
4329
|
}, takeoverGeneration);
|
|
4159
4330
|
if (!result.ok) throw Object.assign(/* @__PURE__ */ new Error("This session has Voice on another device."), { owner: result.owner });
|
|
4160
4331
|
reservations.set(sessionId, holder);
|
|
@@ -4229,18 +4400,23 @@ var GlobalLiveManager = class GlobalLiveManager {
|
|
|
4229
4400
|
};
|
|
4230
4401
|
}
|
|
4231
4402
|
bind(host) {
|
|
4403
|
+
if (this.host && this.host !== host) this.unbind(this.host);
|
|
4232
4404
|
this.host = host;
|
|
4405
|
+
for (const scope of host.sessions()) this.watchNotices(scope);
|
|
4233
4406
|
for (const channel of this.channels.values()) channel.companion.bind(host);
|
|
4234
4407
|
}
|
|
4235
4408
|
unbind(host) {
|
|
4236
4409
|
if (this.host !== host) return;
|
|
4410
|
+
for (const sessionId of this.notices.keys()) this.removeNotices(sessionId);
|
|
4237
4411
|
for (const channel of this.channels.values()) channel.companion.unbind(host);
|
|
4238
4412
|
this.host = void 0;
|
|
4239
4413
|
}
|
|
4240
4414
|
sessionAdded(scope) {
|
|
4415
|
+
this.watchNotices(scope);
|
|
4241
4416
|
for (const channel of this.channels.values()) channel.companion.sessionAdded(scope);
|
|
4242
4417
|
}
|
|
4243
4418
|
sessionRemoved(sessionId) {
|
|
4419
|
+
this.removeNotices(sessionId);
|
|
4244
4420
|
for (const channel of this.channels.values()) channel.companion.sessionRemoved(sessionId);
|
|
4245
4421
|
for (const reservations of this.reservations.values()) {
|
|
4246
4422
|
const holder = reservations.get(sessionId);
|
|
@@ -4252,8 +4428,10 @@ var GlobalLiveManager = class GlobalLiveManager {
|
|
|
4252
4428
|
if (coordinator) this.coordinators.set(deviceId, coordinator);
|
|
4253
4429
|
else this.coordinators.delete(deviceId);
|
|
4254
4430
|
this.channels.get(deviceId)?.companion.setCatalog(coordinator);
|
|
4431
|
+
this.pruneNotices();
|
|
4255
4432
|
}
|
|
4256
4433
|
async refreshSelectedCatalog(deviceId) {
|
|
4434
|
+
this.pruneNotices();
|
|
4257
4435
|
await this.channels.get(deviceId)?.companion.refreshSelectedCatalog();
|
|
4258
4436
|
}
|
|
4259
4437
|
liveSessionId(deviceId) {
|
|
@@ -4304,6 +4482,7 @@ var GlobalLiveManager = class GlobalLiveManager {
|
|
|
4304
4482
|
return Response.json({ error: "Connect this device media before controlling Live Voice." }, { status: 409 });
|
|
4305
4483
|
}
|
|
4306
4484
|
const response = await channel.companion.api.start({}).fetch(request);
|
|
4485
|
+
this.pruneNotices();
|
|
4307
4486
|
if (response.status !== 409) return response;
|
|
4308
4487
|
const conflict = await response.clone().json().catch(() => void 0);
|
|
4309
4488
|
if (conflict?.code !== "VOICE_OWNERSHIP_CONFLICT" || !conflict.owner) return response;
|
|
@@ -4320,6 +4499,7 @@ var GlobalLiveManager = class GlobalLiveManager {
|
|
|
4320
4499
|
this.closed = true;
|
|
4321
4500
|
if (this.cleanupTimer !== void 0) clearTimeout(this.cleanupTimer);
|
|
4322
4501
|
this.cleanupTimer = void 0;
|
|
4502
|
+
for (const sessionId of this.notices.keys()) this.removeNotices(sessionId);
|
|
4323
4503
|
await Promise.all([...this.channels.values()].map((channel) => channel.companion.close()));
|
|
4324
4504
|
for (const agent of this.agents.values()) agent.endpoint.onResult = void 0;
|
|
4325
4505
|
this.channels.clear();
|
|
@@ -4416,6 +4596,11 @@ var VoiceOwnershipCoordinator = class {
|
|
|
4416
4596
|
this.prune();
|
|
4417
4597
|
return [...this.participants.values()].find((participant) => participant.active && participant.sessionId !== exceptSessionId && !participant.sessionId.startsWith("peer/"))?.label;
|
|
4418
4598
|
}
|
|
4599
|
+
/** An actually running local participant, never a staged activation or transfer. */
|
|
4600
|
+
activeSessionId() {
|
|
4601
|
+
this.prune();
|
|
4602
|
+
return [...this.participants.values()].find((participant) => participant.active && !participant.sessionId.startsWith("peer/"))?.sessionId;
|
|
4603
|
+
}
|
|
4419
4604
|
/** Current eligible catalog, including discovered paired targets. */
|
|
4420
4605
|
liveCatalog(sourceSessionId) {
|
|
4421
4606
|
this.prune();
|
|
@@ -9241,7 +9426,26 @@ function createVoiceServer(host, broker, homeDirectory, hubToken, peerAgents, re
|
|
|
9241
9426
|
context.inject([PO_VOICE_LIVE_SERVICE], (scoped) => {
|
|
9242
9427
|
const companion = scoped.get(PO_VOICE_LIVE_SERVICE);
|
|
9243
9428
|
if (!companion) return void 0;
|
|
9244
|
-
|
|
9429
|
+
const lifetime = new AbortController();
|
|
9430
|
+
const detach = companion.attach(execution.sessionId, {
|
|
9431
|
+
handle: (request) => liveAgent.handle(request),
|
|
9432
|
+
get onResult() {
|
|
9433
|
+
return liveAgent.onResult;
|
|
9434
|
+
},
|
|
9435
|
+
set onResult(callback) {
|
|
9436
|
+
liveAgent.onResult = callback;
|
|
9437
|
+
},
|
|
9438
|
+
async narrate(text, deviceId, signal) {
|
|
9439
|
+
const request = (0, _agimon_ai_po_core_narration.createNarrationRequest)(text);
|
|
9440
|
+
const admitted = AbortSignal.any([signal, lifetime.signal]);
|
|
9441
|
+
if (!request || admitted.aborted || closed || mode.selectedMode !== "legacy" || mode.state !== "active" || broker.owner?.deviceId !== deviceId) return;
|
|
9442
|
+
await mode.narrateExternal(request.text, admitted);
|
|
9443
|
+
}
|
|
9444
|
+
});
|
|
9445
|
+
return () => {
|
|
9446
|
+
lifetime.abort();
|
|
9447
|
+
detach();
|
|
9448
|
+
};
|
|
9245
9449
|
});
|
|
9246
9450
|
}
|
|
9247
9451
|
],
|