@skaile/workspaces 6.11.0 → 6.12.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 +6 -0
- package/dist/channels/adapters/msteams/activity.d.ts +2 -2
- package/dist/channels/adapters/msteams/activity.d.ts.map +1 -1
- package/dist/channels/adapters/msteams.js +21 -6
- package/dist/channels/adapters/msteams.js.map +1 -1
- package/dist/channels/index.js +175 -26
- package/dist/channels/index.js.map +1 -1
- package/dist/channels/src/context-batch.d.ts +63 -0
- package/dist/channels/src/context-batch.d.ts.map +1 -0
- package/dist/channels/src/egress/output-mirror.d.ts +7 -0
- package/dist/channels/src/egress/output-mirror.d.ts.map +1 -1
- package/dist/channels/src/engine.d.ts +21 -2
- package/dist/channels/src/engine.d.ts.map +1 -1
- package/dist/channels/src/index.d.ts +1 -0
- package/dist/channels/src/index.d.ts.map +1 -1
- package/dist/channels/src/ports.d.ts +9 -0
- package/dist/channels/src/ports.d.ts.map +1 -1
- package/dist/channels/src/types.d.ts +6 -0
- package/dist/channels/src/types.d.ts.map +1 -1
- package/dist/{chunk-F2IBDVBP.js → chunk-VHRGCLCR.js} +2 -2
- package/dist/{chunk-F2IBDVBP.js.map → chunk-VHRGCLCR.js.map} +1 -1
- package/package.json +1 -1
package/dist/channels/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mentionsAgent } from '../chunk-
|
|
2
|
-
export { CHANNEL_PLATFORMS, makeConversationKey, makeQuestionCard, markdownToTeamsText, mentionsAgent, normalizeMentions, parseConversationKey, teamsFormatConverter } from '../chunk-
|
|
1
|
+
import { mentionsAgent } from '../chunk-VHRGCLCR.js';
|
|
2
|
+
export { CHANNEL_PLATFORMS, makeConversationKey, makeQuestionCard, markdownToTeamsText, mentionsAgent, normalizeMentions, parseConversationKey, teamsFormatConverter } from '../chunk-VHRGCLCR.js';
|
|
3
3
|
import '../chunk-NSBPE2FW.js';
|
|
4
4
|
import { ulid } from 'ulid';
|
|
5
5
|
|
|
@@ -10,6 +10,100 @@ var consoleChannelLogger = {
|
|
|
10
10
|
error: (m, e, d) => console.error(`[channels] ${m}`, e ?? "", d ?? "")
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
// channels/src/context-batch.ts
|
|
14
|
+
var DEFAULT_FLUSH_AFTER_MS = 3e4;
|
|
15
|
+
var DEFAULT_MAX_BATCH = 20;
|
|
16
|
+
var DEFAULT_HOURLY_CAP = 200;
|
|
17
|
+
var HOUR_MS = 36e5;
|
|
18
|
+
var ContextBatcher = class {
|
|
19
|
+
flushAfterMs;
|
|
20
|
+
maxBatch;
|
|
21
|
+
hourlyCap;
|
|
22
|
+
now;
|
|
23
|
+
onDue;
|
|
24
|
+
logger;
|
|
25
|
+
conversationKey;
|
|
26
|
+
pending = [];
|
|
27
|
+
timer;
|
|
28
|
+
windowStart = Number.NEGATIVE_INFINITY;
|
|
29
|
+
acceptedInWindow = 0;
|
|
30
|
+
droppedInWindow = 0;
|
|
31
|
+
constructor(opts) {
|
|
32
|
+
this.flushAfterMs = opts.options?.flushAfterMs ?? DEFAULT_FLUSH_AFTER_MS;
|
|
33
|
+
this.maxBatch = opts.options?.maxBatch ?? DEFAULT_MAX_BATCH;
|
|
34
|
+
this.hourlyCap = opts.options?.hourlyCap ?? DEFAULT_HOURLY_CAP;
|
|
35
|
+
this.now = opts.now;
|
|
36
|
+
this.onDue = opts.onDue;
|
|
37
|
+
this.logger = opts.logger;
|
|
38
|
+
this.conversationKey = opts.conversationKey;
|
|
39
|
+
}
|
|
40
|
+
/** Messages waiting for the next flush. */
|
|
41
|
+
get size() {
|
|
42
|
+
return this.pending.length;
|
|
43
|
+
}
|
|
44
|
+
/** Buffers `msg` unless the hourly cap is spent. `"full"` means flush now. */
|
|
45
|
+
add(msg) {
|
|
46
|
+
this.rollWindow();
|
|
47
|
+
if (this.acceptedInWindow >= this.hourlyCap) {
|
|
48
|
+
this.droppedInWindow += 1;
|
|
49
|
+
if (this.droppedInWindow === 1) {
|
|
50
|
+
this.logger.warn(
|
|
51
|
+
"read-along context cap reached; dropping context for the rest of the hour",
|
|
52
|
+
{
|
|
53
|
+
conversationKey: this.conversationKey,
|
|
54
|
+
hourlyCap: this.hourlyCap
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return "dropped";
|
|
59
|
+
}
|
|
60
|
+
this.acceptedInWindow += 1;
|
|
61
|
+
this.pending.push(msg);
|
|
62
|
+
if (this.pending.length >= this.maxBatch) return "full";
|
|
63
|
+
if (this.timer === void 0) {
|
|
64
|
+
this.timer = setTimeout(() => {
|
|
65
|
+
this.timer = void 0;
|
|
66
|
+
this.onDue();
|
|
67
|
+
}, this.flushAfterMs);
|
|
68
|
+
}
|
|
69
|
+
return "buffered";
|
|
70
|
+
}
|
|
71
|
+
/** Drains the pending batch (possibly empty) and cancels its timer. */
|
|
72
|
+
take() {
|
|
73
|
+
this.clearTimer();
|
|
74
|
+
const batch = this.pending;
|
|
75
|
+
this.pending = [];
|
|
76
|
+
return batch;
|
|
77
|
+
}
|
|
78
|
+
/** Cancels the timer and logs any drops still unreported. Pending messages are left to `take()`. */
|
|
79
|
+
dispose() {
|
|
80
|
+
this.clearTimer();
|
|
81
|
+
this.reportDropped();
|
|
82
|
+
}
|
|
83
|
+
rollWindow() {
|
|
84
|
+
const now = this.now();
|
|
85
|
+
if (now - this.windowStart < HOUR_MS) return;
|
|
86
|
+
this.reportDropped();
|
|
87
|
+
this.windowStart = now;
|
|
88
|
+
this.acceptedInWindow = 0;
|
|
89
|
+
this.droppedInWindow = 0;
|
|
90
|
+
}
|
|
91
|
+
reportDropped() {
|
|
92
|
+
if (this.droppedInWindow === 0) return;
|
|
93
|
+
this.logger.warn("read-along context dropped over the hourly cap", {
|
|
94
|
+
conversationKey: this.conversationKey,
|
|
95
|
+
dropped: this.droppedInWindow,
|
|
96
|
+
hourlyCap: this.hourlyCap
|
|
97
|
+
});
|
|
98
|
+
this.droppedInWindow = 0;
|
|
99
|
+
}
|
|
100
|
+
clearTimer() {
|
|
101
|
+
if (this.timer === void 0) return;
|
|
102
|
+
clearTimeout(this.timer);
|
|
103
|
+
this.timer = void 0;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
13
107
|
// channels/src/dedupe.ts
|
|
14
108
|
var TtlDedupeStore = class {
|
|
15
109
|
ttlMs;
|
|
@@ -434,6 +528,9 @@ var ChunkedMessageStream = class {
|
|
|
434
528
|
|
|
435
529
|
// channels/src/egress/output-mirror.ts
|
|
436
530
|
var TYPING_THROTTLE_MS = 3e3;
|
|
531
|
+
function endsTurn(e) {
|
|
532
|
+
return e.type === "finished" || e.type === "status" && e.phase === "idle";
|
|
533
|
+
}
|
|
437
534
|
var OutputMirror = class {
|
|
438
535
|
opts;
|
|
439
536
|
turn;
|
|
@@ -463,6 +560,9 @@ var OutputMirror = class {
|
|
|
463
560
|
case "error":
|
|
464
561
|
this.postAside({ markdown: e.message, kind: "error" });
|
|
465
562
|
break;
|
|
563
|
+
case "status":
|
|
564
|
+
if (endsTurn(e)) this.finishTurn();
|
|
565
|
+
break;
|
|
466
566
|
case "typing":
|
|
467
567
|
this.onTyping(e.isTyping);
|
|
468
568
|
break;
|
|
@@ -635,6 +735,7 @@ function inboundMessageRef(ch) {
|
|
|
635
735
|
};
|
|
636
736
|
}
|
|
637
737
|
var textOf = (msg) => msg.payload.type === "prompt" ? msg.payload.prompt : "";
|
|
738
|
+
var isMentioned = (msg, ch) => ch.mentioned ?? mentionsAgent(msg.meta?.mentions ?? []);
|
|
638
739
|
function withSenderId(msg, senderId) {
|
|
639
740
|
return { ...msg, meta: { ...msg.meta, senderId } };
|
|
640
741
|
}
|
|
@@ -831,10 +932,10 @@ var ChannelEngine = class {
|
|
|
831
932
|
tenantId: ch.tenantId
|
|
832
933
|
};
|
|
833
934
|
const host = await ports.identity.resolveActor(actor);
|
|
935
|
+
const mentioned = isMentioned(evt, ch);
|
|
834
936
|
let binding = await ports.bindings.get(ch.conversationKey);
|
|
835
937
|
if (!binding?.enabled) {
|
|
836
|
-
|
|
837
|
-
if (!mentionsAgent(mentions) || !ports.session.createSession) return;
|
|
938
|
+
if (!mentioned || !ports.session.createSession) return;
|
|
838
939
|
if (host === null && this.unmappedActorPolicy === "reject") {
|
|
839
940
|
await this.postRejectNotice(adapter, ch, actor);
|
|
840
941
|
return;
|
|
@@ -851,13 +952,17 @@ var ChannelEngine = class {
|
|
|
851
952
|
sessionId: ref.sessionId,
|
|
852
953
|
platform: ch.platform,
|
|
853
954
|
tenantId: ch.tenantId,
|
|
854
|
-
mirrorMode: "agent-only",
|
|
955
|
+
mirrorMode: this.opts.lazyBindMirrorMode ?? "agent-only",
|
|
855
956
|
enabled: true,
|
|
856
957
|
createdBy: host?.id ?? `external:${actor.externalId}`,
|
|
857
958
|
createdAt: new Date(this.now()).toISOString()
|
|
858
959
|
};
|
|
859
960
|
await ports.bindings.put(binding);
|
|
860
961
|
}
|
|
962
|
+
if (!mentioned && ports.session.appendContext) {
|
|
963
|
+
await this.appendAsContext(binding, evt, actor, host);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
861
966
|
let delivery = evt;
|
|
862
967
|
if (host === null) {
|
|
863
968
|
if (this.unmappedActorPolicy === "reject") {
|
|
@@ -870,11 +975,41 @@ var ChannelEngine = class {
|
|
|
870
975
|
}
|
|
871
976
|
this.ensureMirror(binding);
|
|
872
977
|
const state = this.stateFor(binding);
|
|
978
|
+
await this.flushContext(state);
|
|
873
979
|
state.delivered.set(textOf(delivery), true);
|
|
874
980
|
state.chatInitiated = true;
|
|
875
981
|
await ports.session.deliver(binding, delivery);
|
|
876
982
|
this.armPlaceholder(binding, state);
|
|
877
983
|
}
|
|
984
|
+
/**
|
|
985
|
+
* Read-along: queues a message that did not mention the bot as session
|
|
986
|
+
* context. It never starts a turn, so an unmapped actor needs no reject
|
|
987
|
+
* notice: the message is framed as untrusted and carries no senderId.
|
|
988
|
+
*/
|
|
989
|
+
async appendAsContext(binding, evt, actor, host) {
|
|
990
|
+
if (textOf(evt).trim() === "") return;
|
|
991
|
+
const msg = host === null ? frameAsExternal(evt, actor) : withSenderId(evt, host.id);
|
|
992
|
+
this.ensureMirror(binding);
|
|
993
|
+
const state = this.stateFor(binding);
|
|
994
|
+
const added = state.context.add(msg);
|
|
995
|
+
if (added === "dropped") return;
|
|
996
|
+
state.delivered.set(textOf(msg), true);
|
|
997
|
+
if (added === "full") await this.flushContext(state);
|
|
998
|
+
}
|
|
999
|
+
/** Hands the pending read-along batch to `SessionPort.appendContext`; logs, never throws. */
|
|
1000
|
+
async flushContext(state) {
|
|
1001
|
+
const appendContext = this.opts.ports.session.appendContext;
|
|
1002
|
+
const batch = state.context.take();
|
|
1003
|
+
if (batch.length === 0 || !appendContext) return;
|
|
1004
|
+
try {
|
|
1005
|
+
await appendContext.call(this.opts.ports.session, state.binding, batch);
|
|
1006
|
+
} catch (err) {
|
|
1007
|
+
this.logger.error("appendContext failed; read-along context lost", err, {
|
|
1008
|
+
conversationKey: state.binding.conversationKey,
|
|
1009
|
+
messages: batch.length
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
878
1013
|
/** "Teams: <snippet>" — title for a lazily-created thread session. */
|
|
879
1014
|
sessionTitle(ch, text) {
|
|
880
1015
|
const snippet = text.trim().slice(0, 60) || ch.conversationKey;
|
|
@@ -898,24 +1033,34 @@ var ChannelEngine = class {
|
|
|
898
1033
|
this.egressRefs.set(egressRefKey(ref), true);
|
|
899
1034
|
}
|
|
900
1035
|
stateFor(binding) {
|
|
901
|
-
|
|
902
|
-
if (
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
|
|
1036
|
+
const existing = this.states.get(binding.conversationKey);
|
|
1037
|
+
if (existing) return existing;
|
|
1038
|
+
const key = binding.conversationKey;
|
|
1039
|
+
const state = {
|
|
1040
|
+
binding,
|
|
1041
|
+
chatInitiated: false,
|
|
1042
|
+
delivered: new TtlCache({
|
|
1043
|
+
ttlMs: DELIVERED_CONTENT_TTL_MS,
|
|
1044
|
+
maxEntries: DELIVERED_CONTENT_MAX_ENTRIES,
|
|
1045
|
+
now: this.now
|
|
1046
|
+
}),
|
|
1047
|
+
replyRefs: new TtlCache({
|
|
1048
|
+
ttlMs: REPLY_REF_TTL_MS,
|
|
1049
|
+
maxEntries: REPLY_REF_MAX_ENTRIES,
|
|
1050
|
+
now: this.now
|
|
1051
|
+
}),
|
|
1052
|
+
context: new ContextBatcher({
|
|
1053
|
+
options: this.opts.contextBatch,
|
|
1054
|
+
conversationKey: key,
|
|
1055
|
+
now: this.now,
|
|
1056
|
+
logger: this.logger,
|
|
1057
|
+
// Queued on the conversation's chain, so a timed flush never overtakes a prompt.
|
|
1058
|
+
onDue: () => {
|
|
1059
|
+
void this.enqueueOnKey(key, () => this.flushContext(state));
|
|
1060
|
+
}
|
|
1061
|
+
})
|
|
1062
|
+
};
|
|
1063
|
+
this.states.set(key, state);
|
|
919
1064
|
return state;
|
|
920
1065
|
}
|
|
921
1066
|
/** Idempotent: one OutputMirror + session subscription per bound conversation. */
|
|
@@ -943,7 +1088,9 @@ var ChannelEngine = class {
|
|
|
943
1088
|
}
|
|
944
1089
|
/** Engine-side filtering in front of the mirror: placeholder, loop guard, turn origin. */
|
|
945
1090
|
onAgentEvent(state, mirror, e) {
|
|
946
|
-
if (e.type === "text" || e.type === "finished"
|
|
1091
|
+
if (e.type === "text" || e.type === "finished" || e.type === "error") {
|
|
1092
|
+
this.disarmPlaceholder(state);
|
|
1093
|
+
}
|
|
947
1094
|
if (e.type === "user_message" && state.delivered.has(e.content)) return;
|
|
948
1095
|
if (state.binding.mirrorMode === "chat-initiated" && !state.chatInitiated && (e.type === "text" || e.type === "text_retract" || e.type === "question" || e.type === "error" || e.type === "typing")) {
|
|
949
1096
|
return;
|
|
@@ -993,8 +1140,10 @@ var ChannelEngine = class {
|
|
|
993
1140
|
});
|
|
994
1141
|
}
|
|
995
1142
|
}
|
|
996
|
-
/**
|
|
1143
|
+
/** Flush pending context, disarm, unsubscribe, and flush/dispose the mirror of one binding. */
|
|
997
1144
|
async teardownState(state) {
|
|
1145
|
+
await this.flushContext(state);
|
|
1146
|
+
state.context.dispose();
|
|
998
1147
|
this.disarmPlaceholder(state);
|
|
999
1148
|
state.unsubscribe?.();
|
|
1000
1149
|
state.unsubscribe = void 0;
|
|
@@ -1088,6 +1237,6 @@ function buildLookupUserTool(adapter, renderer) {
|
|
|
1088
1237
|
}
|
|
1089
1238
|
var LOOKUP_USER_PROMPT_FRAGMENT = "To @-mention a person in the chat channel, first call the lookup_user tool with their name, email, or id, then paste the returned mentionToken into your message verbatim \u2014 do not alter or re-format it. If lookup_user finds nobody, refer to the person by plain name instead.";
|
|
1090
1239
|
|
|
1091
|
-
export { ChannelEngine, LOOKUP_USER_PROMPT_FRAGMENT, OutputMirror, REJECT_NOTICE, TtlCache, TtlDedupeStore, buildLookupUserTool, consoleChannelLogger, mrkdwnFormatConverter };
|
|
1240
|
+
export { ChannelEngine, ContextBatcher, LOOKUP_USER_PROMPT_FRAGMENT, OutputMirror, REJECT_NOTICE, TtlCache, TtlDedupeStore, buildLookupUserTool, consoleChannelLogger, endsTurn, mrkdwnFormatConverter };
|
|
1092
1241
|
//# sourceMappingURL=index.js.map
|
|
1093
1242
|
//# sourceMappingURL=index.js.map
|