@krischoichoi/channel-harness 0.6.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/README.md +110 -0
- package/lib/access/controller.d.ts +37 -0
- package/lib/access/controller.d.ts.map +1 -0
- package/lib/access/controller.js +51 -0
- package/lib/access/controller.js.map +1 -0
- package/lib/access/decision.d.ts +18 -0
- package/lib/access/decision.d.ts.map +1 -0
- package/lib/access/decision.js +2 -0
- package/lib/access/decision.js.map +1 -0
- package/lib/access/resolver.d.ts +41 -0
- package/lib/access/resolver.d.ts.map +1 -0
- package/lib/access/resolver.js +31 -0
- package/lib/access/resolver.js.map +1 -0
- package/lib/agent-manager.d.ts +314 -0
- package/lib/agent-manager.d.ts.map +1 -0
- package/lib/agent-manager.js +603 -0
- package/lib/agent-manager.js.map +1 -0
- package/lib/agent-router.d.ts +37 -0
- package/lib/agent-router.d.ts.map +1 -0
- package/lib/agent-router.js +33 -0
- package/lib/agent-router.js.map +1 -0
- package/lib/bind-support.d.ts +23 -0
- package/lib/bind-support.d.ts.map +1 -0
- package/lib/bind-support.js +63 -0
- package/lib/bind-support.js.map +1 -0
- package/lib/binding-store.d.ts +104 -0
- package/lib/binding-store.d.ts.map +1 -0
- package/lib/binding-store.js +354 -0
- package/lib/binding-store.js.map +1 -0
- package/lib/bridge.d.ts +248 -0
- package/lib/bridge.d.ts.map +1 -0
- package/lib/bridge.js +961 -0
- package/lib/bridge.js.map +1 -0
- package/lib/channel-label.d.ts +46 -0
- package/lib/channel-label.d.ts.map +1 -0
- package/lib/channel-label.js +89 -0
- package/lib/channel-label.js.map +1 -0
- package/lib/channel-session-factory.d.ts +76 -0
- package/lib/channel-session-factory.d.ts.map +1 -0
- package/lib/channel-session-factory.js +187 -0
- package/lib/channel-session-factory.js.map +1 -0
- package/lib/commands/bind.d.ts +21 -0
- package/lib/commands/bind.d.ts.map +1 -0
- package/lib/commands/bind.js +91 -0
- package/lib/commands/bind.js.map +1 -0
- package/lib/commands/help.d.ts +15 -0
- package/lib/commands/help.d.ts.map +1 -0
- package/lib/commands/help.js +89 -0
- package/lib/commands/help.js.map +1 -0
- package/lib/commands/index.d.ts +122 -0
- package/lib/commands/index.d.ts.map +1 -0
- package/lib/commands/index.js +63 -0
- package/lib/commands/index.js.map +1 -0
- package/lib/commands/locale.d.ts +9 -0
- package/lib/commands/locale.d.ts.map +1 -0
- package/lib/commands/locale.js +27 -0
- package/lib/commands/locale.js.map +1 -0
- package/lib/commands/mirror.d.ts +14 -0
- package/lib/commands/mirror.d.ts.map +1 -0
- package/lib/commands/mirror.js +44 -0
- package/lib/commands/mirror.js.map +1 -0
- package/lib/commands/model.d.ts +20 -0
- package/lib/commands/model.d.ts.map +1 -0
- package/lib/commands/model.js +106 -0
- package/lib/commands/model.js.map +1 -0
- package/lib/commands/models.d.ts +14 -0
- package/lib/commands/models.d.ts.map +1 -0
- package/lib/commands/models.js +67 -0
- package/lib/commands/models.js.map +1 -0
- package/lib/commands/new.d.ts +14 -0
- package/lib/commands/new.d.ts.map +1 -0
- package/lib/commands/new.js +37 -0
- package/lib/commands/new.js.map +1 -0
- package/lib/commands/status.d.ts +12 -0
- package/lib/commands/status.d.ts.map +1 -0
- package/lib/commands/status.js +30 -0
- package/lib/commands/status.js.map +1 -0
- package/lib/commands/stop.d.ts +16 -0
- package/lib/commands/stop.d.ts.map +1 -0
- package/lib/commands/stop.js +27 -0
- package/lib/commands/stop.js.map +1 -0
- package/lib/commands/version.d.ts +25 -0
- package/lib/commands/version.d.ts.map +1 -0
- package/lib/commands/version.js +57 -0
- package/lib/commands/version.js.map +1 -0
- package/lib/config.d.ts +119 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +60 -0
- package/lib/config.js.map +1 -0
- package/lib/debug-logger.d.ts +8 -0
- package/lib/debug-logger.d.ts.map +1 -0
- package/lib/debug-logger.js +32 -0
- package/lib/debug-logger.js.map +1 -0
- package/lib/dsh-home.d.ts +12 -0
- package/lib/dsh-home.d.ts.map +1 -0
- package/lib/dsh-home.js +31 -0
- package/lib/dsh-home.js.map +1 -0
- package/lib/failure-display.d.ts +10 -0
- package/lib/failure-display.d.ts.map +1 -0
- package/lib/failure-display.js +42 -0
- package/lib/failure-display.js.map +1 -0
- package/lib/file-provider.d.ts +96 -0
- package/lib/file-provider.d.ts.map +1 -0
- package/lib/file-provider.js +44 -0
- package/lib/file-provider.js.map +1 -0
- package/lib/index.d.ts +31 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +31 -0
- package/lib/index.js.map +1 -0
- package/lib/interactions/index.d.ts +13 -0
- package/lib/interactions/index.d.ts.map +1 -0
- package/lib/interactions/index.js +13 -0
- package/lib/interactions/index.js.map +1 -0
- package/lib/interactions/question-backend.d.ts +133 -0
- package/lib/interactions/question-backend.d.ts.map +1 -0
- package/lib/interactions/question-backend.js +37 -0
- package/lib/interactions/question-backend.js.map +1 -0
- package/lib/interactions/question-presenter.d.ts +68 -0
- package/lib/interactions/question-presenter.d.ts.map +1 -0
- package/lib/interactions/question-presenter.js +425 -0
- package/lib/interactions/question-presenter.js.map +1 -0
- package/lib/interactions/question-renderer.d.ts +37 -0
- package/lib/interactions/question-renderer.d.ts.map +1 -0
- package/lib/interactions/question-renderer.js +79 -0
- package/lib/interactions/question-renderer.js.map +1 -0
- package/lib/interactions/question-state.d.ts +106 -0
- package/lib/interactions/question-state.d.ts.map +1 -0
- package/lib/interactions/question-state.js +99 -0
- package/lib/interactions/question-state.js.map +1 -0
- package/lib/interactions/question-text-answer.d.ts +37 -0
- package/lib/interactions/question-text-answer.d.ts.map +1 -0
- package/lib/interactions/question-text-answer.js +70 -0
- package/lib/interactions/question-text-answer.js.map +1 -0
- package/lib/interactions/question-waterfall-backend.d.ts +42 -0
- package/lib/interactions/question-waterfall-backend.d.ts.map +1 -0
- package/lib/interactions/question-waterfall-backend.js +162 -0
- package/lib/interactions/question-waterfall-backend.js.map +1 -0
- package/lib/lifecycle.d.ts +36 -0
- package/lib/lifecycle.d.ts.map +1 -0
- package/lib/lifecycle.js +303 -0
- package/lib/lifecycle.js.map +1 -0
- package/lib/loggable-error.d.ts +10 -0
- package/lib/loggable-error.d.ts.map +1 -0
- package/lib/loggable-error.js +32 -0
- package/lib/loggable-error.js.map +1 -0
- package/lib/message-converter.d.ts +64 -0
- package/lib/message-converter.d.ts.map +1 -0
- package/lib/message-converter.js +191 -0
- package/lib/message-converter.js.map +1 -0
- package/lib/model-selection.d.ts +54 -0
- package/lib/model-selection.d.ts.map +1 -0
- package/lib/model-selection.js +116 -0
- package/lib/model-selection.js.map +1 -0
- package/lib/outbox/binding-resolver.d.ts +23 -0
- package/lib/outbox/binding-resolver.d.ts.map +1 -0
- package/lib/outbox/binding-resolver.js +38 -0
- package/lib/outbox/binding-resolver.js.map +1 -0
- package/lib/outbox/capabilities.d.ts +48 -0
- package/lib/outbox/capabilities.d.ts.map +1 -0
- package/lib/outbox/capabilities.js +35 -0
- package/lib/outbox/capabilities.js.map +1 -0
- package/lib/outbox/index.d.ts +14 -0
- package/lib/outbox/index.d.ts.map +1 -0
- package/lib/outbox/index.js +14 -0
- package/lib/outbox/index.js.map +1 -0
- package/lib/outbox/service.d.ts +48 -0
- package/lib/outbox/service.d.ts.map +1 -0
- package/lib/outbox/service.js +74 -0
- package/lib/outbox/service.js.map +1 -0
- package/lib/outbox/target.d.ts +17 -0
- package/lib/outbox/target.d.ts.map +1 -0
- package/lib/outbox/target.js +15 -0
- package/lib/outbox/target.js.map +1 -0
- package/lib/outbox/tool-send.d.ts +19 -0
- package/lib/outbox/tool-send.d.ts.map +1 -0
- package/lib/outbox/tool-send.js +77 -0
- package/lib/outbox/tool-send.js.map +1 -0
- package/lib/outbox/types.d.ts +42 -0
- package/lib/outbox/types.d.ts.map +1 -0
- package/lib/outbox/types.js +30 -0
- package/lib/outbox/types.js.map +1 -0
- package/lib/plugin.d.ts +27 -0
- package/lib/plugin.d.ts.map +1 -0
- package/lib/plugin.js +47 -0
- package/lib/plugin.js.map +1 -0
- package/lib/reply-context-store.d.ts +69 -0
- package/lib/reply-context-store.d.ts.map +1 -0
- package/lib/reply-context-store.js +75 -0
- package/lib/reply-context-store.js.map +1 -0
- package/lib/reply-router.d.ts +138 -0
- package/lib/reply-router.d.ts.map +1 -0
- package/lib/reply-router.js +674 -0
- package/lib/reply-router.js.map +1 -0
- package/lib/session-router.d.ts +49 -0
- package/lib/session-router.d.ts.map +1 -0
- package/lib/session-router.js +45 -0
- package/lib/session-router.js.map +1 -0
- package/lib/workspace-resolver.d.ts +67 -0
- package/lib/workspace-resolver.d.ts.map +1 -0
- package/lib/workspace-resolver.js +102 -0
- package/lib/workspace-resolver.js.map +1 -0
- package/package.json +103 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { AskUserQuestionAnswerItem, AskUserQuestionItem } from '@deepseek-ai/dsh-user-questions/types';
|
|
2
|
+
import type { ChannelTarget } from '@krischoichoi/channel-core';
|
|
3
|
+
/**
|
|
4
|
+
* How a question batch is presented on a channel conversation:
|
|
5
|
+
* - `'actions'` — the adapter supports `interactiveActions`; native buttons.
|
|
6
|
+
* - `'text'` — text-only fallback; numbered options + free-text answers.
|
|
7
|
+
*
|
|
8
|
+
* Internal to `channel-harness`; NOT part of the public Channel Contract.
|
|
9
|
+
* Decided by capability negotiation (never by channel id).
|
|
10
|
+
*/
|
|
11
|
+
export type QuestionPresentationMode = 'actions' | 'text';
|
|
12
|
+
/**
|
|
13
|
+
* Mint a short correlation token for a text-presented question (e.g.
|
|
14
|
+
* `Q-A13F7C`). It is a route/typing correlate for group/thread replies only,
|
|
15
|
+
* NEVER an auth credential — Authorization stays with the Access Gate +
|
|
16
|
+
* `allowedSenderId`. Kept short (not a UUID) so it is comfortable to type in
|
|
17
|
+
* group chat.
|
|
18
|
+
*/
|
|
19
|
+
export declare function newReplyToken(prefix?: string): string;
|
|
20
|
+
/** What one bound channel action button does when pressed. */
|
|
21
|
+
export interface PendingQuestionAction {
|
|
22
|
+
kind: 'option' | 'custom' | 'done' | 'skip';
|
|
23
|
+
optionIndex?: number;
|
|
24
|
+
}
|
|
25
|
+
/** A bound action button: opaque channel-facing id -> its pending question. */
|
|
26
|
+
export interface PendingQuestionActionRef {
|
|
27
|
+
pending: PendingChannelQuestion;
|
|
28
|
+
action: PendingQuestionAction;
|
|
29
|
+
}
|
|
30
|
+
/** Explicit lifecycle so removed objects held by an async handler stay terminal. */
|
|
31
|
+
export type QuestionLifecycleState = 'pending' | 'resolved' | 'expired' | 'aborted' | 'externally-settled';
|
|
32
|
+
/**
|
|
33
|
+
* One in-flight question batch as presented on one channel conversation.
|
|
34
|
+
* Transport-neutral: `key` is whatever correlation id the active backend
|
|
35
|
+
* minted (the waterfall ask key today).
|
|
36
|
+
*/
|
|
37
|
+
export interface PendingChannelQuestion {
|
|
38
|
+
/** Backend correlation key; echoed back on resolve/cancel. */
|
|
39
|
+
key: string;
|
|
40
|
+
sessionId: string;
|
|
41
|
+
/** Official question items, fields carried verbatim (incl. `intent`). */
|
|
42
|
+
questions: AskUserQuestionItem[];
|
|
43
|
+
answers: AskUserQuestionAnswerItem[];
|
|
44
|
+
questionIndex: number;
|
|
45
|
+
selected: Set<string>;
|
|
46
|
+
awaitingCustom: boolean;
|
|
47
|
+
state: QuestionLifecycleState;
|
|
48
|
+
processing: boolean;
|
|
49
|
+
responding: boolean;
|
|
50
|
+
target: ChannelTarget;
|
|
51
|
+
conversationKey: string;
|
|
52
|
+
/**
|
|
53
|
+
* Only the sender whose inbound turn created the active reply context may
|
|
54
|
+
* answer (existing access semantics — authorization already happened at the
|
|
55
|
+
* bridge Access Gate before the question was presented).
|
|
56
|
+
*/
|
|
57
|
+
allowedSenderId: string;
|
|
58
|
+
actionIds: Set<string>;
|
|
59
|
+
/** How this batch is presented (capability-negotiated, never channel id). */
|
|
60
|
+
presentationMode: QuestionPresentationMode;
|
|
61
|
+
/**
|
|
62
|
+
* Short correlation token for group/thread text replies when the platform
|
|
63
|
+
* cannot (or does not) map `replyTo` reliably. Regenerated per question via
|
|
64
|
+
* {@link advance}. Not a secret; never a substitute for authorization.
|
|
65
|
+
*/
|
|
66
|
+
replyToken?: string;
|
|
67
|
+
/** Message that a free-text response must reply to in a group or forum. */
|
|
68
|
+
promptMessageId?: string;
|
|
69
|
+
messageId?: string;
|
|
70
|
+
renderedText?: string;
|
|
71
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Registry + timers for in-flight channel questions. All lookups are
|
|
75
|
+
* synchronous; the class never touches adapters or backends.
|
|
76
|
+
*/
|
|
77
|
+
export declare class QuestionStateStore {
|
|
78
|
+
private readonly byKey;
|
|
79
|
+
private readonly byConversation;
|
|
80
|
+
private readonly actions;
|
|
81
|
+
/**
|
|
82
|
+
* Register a freshly built pending question. Returns false (and registers
|
|
83
|
+
* nothing) when either its key was already presented (mux replay of a
|
|
84
|
+
* still-pending question — rpcId is reused verbatim on stream reopen) or
|
|
85
|
+
* its conversation already has a pending question.
|
|
86
|
+
*/
|
|
87
|
+
register(pending: PendingChannelQuestion): boolean;
|
|
88
|
+
getByKey(key: string): PendingChannelQuestion | undefined;
|
|
89
|
+
getByConversation(conversationKey: string): PendingChannelQuestion | undefined;
|
|
90
|
+
/** Snapshot of every in-flight question (stop()/cancel-all iteration). */
|
|
91
|
+
all(): PendingChannelQuestion[];
|
|
92
|
+
/**
|
|
93
|
+
* Mint a bounded, opaque channel-facing action id and bind it. Ids stay
|
|
94
|
+
* within platform callback-payload budgets (e.g. Telegram callback_data).
|
|
95
|
+
*/
|
|
96
|
+
bindAction(pending: PendingChannelQuestion, action: PendingQuestionAction): string;
|
|
97
|
+
findAction(actionId: string): PendingQuestionActionRef | undefined;
|
|
98
|
+
/** Drop every bound action of a pending question (re-render / settle). */
|
|
99
|
+
clearActions(pending: PendingChannelQuestion): void;
|
|
100
|
+
/** Arm the one-shot cancel timer for a pending question. */
|
|
101
|
+
armTimeout(pending: PendingChannelQuestion, timeoutMs: number, onTimeout: (pending: PendingChannelQuestion) => void): void;
|
|
102
|
+
clearTimer(pending: PendingChannelQuestion): void;
|
|
103
|
+
/** Fully unregister a settled question (timer + both indexes). */
|
|
104
|
+
remove(pending: PendingChannelQuestion): void;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=question-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question-state.d.ts","sourceRoot":"","sources":["../../src/interactions/question-state.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACV,yBAAyB,EACzB,mBAAmB,EACpB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEhE;;;;;;;GAOG;AACH,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,MAAM,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,SAAO,GAAG,MAAM,CAGnD;AAED,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,+EAA+E;AAC/E,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,sBAAsB,CAAC;IAChC,MAAM,EAAE,qBAAqB,CAAC;CAC/B;AAED,oFAAoF;AACpF,MAAM,MAAM,sBAAsB,GAC9B,SAAS,GACT,UAAU,GACV,SAAS,GACT,SAAS,GACT,oBAAoB,CAAC;AAEzB;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,8DAA8D;IAC9D,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,OAAO,EAAE,yBAAyB,EAAE,CAAC;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtB,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,EAAE,sBAAsB,CAAC;IAC9B,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,6EAA6E;IAC7E,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;CACvC;AAED;;;GAGG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6C;IACnE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6C;IAC5E,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+C;IAEvE;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO;IAQlD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,sBAAsB,GAAG,SAAS;IAIzD,iBAAiB,CAAC,eAAe,EAAE,MAAM,GAAG,sBAAsB,GAAG,SAAS;IAI9E,0EAA0E;IAC1E,GAAG,IAAI,sBAAsB,EAAE;IAI/B;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,sBAAsB,EAAE,MAAM,EAAE,qBAAqB,GAAG,MAAM;IAOlF,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,wBAAwB,GAAG,SAAS;IAIlE,0EAA0E;IAC1E,YAAY,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI;IAKnD,4DAA4D;IAC5D,UAAU,CACR,OAAO,EAAE,sBAAsB,EAC/B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,GACnD,IAAI;IAKP,UAAU,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI;IAKjD,kEAAkE;IAClE,MAAM,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI;CAO9C"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending-question state machine for channel question interactions.
|
|
3
|
+
*
|
|
4
|
+
* Owns exactly the runtime bookkeeping of an in-flight question batch:
|
|
5
|
+
* the pending registry (by backend correlation key and by channel
|
|
6
|
+
* conversation — one pending question per conversation at a time), the
|
|
7
|
+
* action-button binding table, and the cancel timer. Rendering, channel
|
|
8
|
+
* binding and answer collection live in `question-presenter.ts`; the
|
|
9
|
+
* Harness-side transport lives in the backend modules.
|
|
10
|
+
*
|
|
11
|
+
* Question/answer shapes are the official `dsh-user-questions` types
|
|
12
|
+
* (`AskUserQuestionItem` / `AskUserQuestionAnswerItem`) — every official
|
|
13
|
+
* field (`detail` / `header` / `options` / `multiSelect` / `intent`) is
|
|
14
|
+
* carried verbatim; nothing is stripped or re-encoded here.
|
|
15
|
+
*/
|
|
16
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
17
|
+
/**
|
|
18
|
+
* Mint a short correlation token for a text-presented question (e.g.
|
|
19
|
+
* `Q-A13F7C`). It is a route/typing correlate for group/thread replies only,
|
|
20
|
+
* NEVER an auth credential — Authorization stays with the Access Gate +
|
|
21
|
+
* `allowedSenderId`. Kept short (not a UUID) so it is comfortable to type in
|
|
22
|
+
* group chat.
|
|
23
|
+
*/
|
|
24
|
+
export function newReplyToken(prefix = 'Q-') {
|
|
25
|
+
const hex = randomBytes(3).toString('hex').toUpperCase();
|
|
26
|
+
return `${prefix}${hex}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Registry + timers for in-flight channel questions. All lookups are
|
|
30
|
+
* synchronous; the class never touches adapters or backends.
|
|
31
|
+
*/
|
|
32
|
+
export class QuestionStateStore {
|
|
33
|
+
byKey = new Map();
|
|
34
|
+
byConversation = new Map();
|
|
35
|
+
actions = new Map();
|
|
36
|
+
/**
|
|
37
|
+
* Register a freshly built pending question. Returns false (and registers
|
|
38
|
+
* nothing) when either its key was already presented (mux replay of a
|
|
39
|
+
* still-pending question — rpcId is reused verbatim on stream reopen) or
|
|
40
|
+
* its conversation already has a pending question.
|
|
41
|
+
*/
|
|
42
|
+
register(pending) {
|
|
43
|
+
if (this.byKey.has(pending.key))
|
|
44
|
+
return false;
|
|
45
|
+
if (this.byConversation.has(pending.conversationKey))
|
|
46
|
+
return false;
|
|
47
|
+
this.byKey.set(pending.key, pending);
|
|
48
|
+
this.byConversation.set(pending.conversationKey, pending);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
getByKey(key) {
|
|
52
|
+
return this.byKey.get(key);
|
|
53
|
+
}
|
|
54
|
+
getByConversation(conversationKey) {
|
|
55
|
+
return this.byConversation.get(conversationKey);
|
|
56
|
+
}
|
|
57
|
+
/** Snapshot of every in-flight question (stop()/cancel-all iteration). */
|
|
58
|
+
all() {
|
|
59
|
+
return [...this.byKey.values()];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Mint a bounded, opaque channel-facing action id and bind it. Ids stay
|
|
63
|
+
* within platform callback-payload budgets (e.g. Telegram callback_data).
|
|
64
|
+
*/
|
|
65
|
+
bindAction(pending, action) {
|
|
66
|
+
const id = `uq_${randomUUID().replaceAll('-', '')}`;
|
|
67
|
+
pending.actionIds.add(id);
|
|
68
|
+
this.actions.set(id, { pending, action });
|
|
69
|
+
return id;
|
|
70
|
+
}
|
|
71
|
+
findAction(actionId) {
|
|
72
|
+
return this.actions.get(actionId);
|
|
73
|
+
}
|
|
74
|
+
/** Drop every bound action of a pending question (re-render / settle). */
|
|
75
|
+
clearActions(pending) {
|
|
76
|
+
for (const id of pending.actionIds)
|
|
77
|
+
this.actions.delete(id);
|
|
78
|
+
pending.actionIds.clear();
|
|
79
|
+
}
|
|
80
|
+
/** Arm the one-shot cancel timer for a pending question. */
|
|
81
|
+
armTimeout(pending, timeoutMs, onTimeout) {
|
|
82
|
+
this.clearTimer(pending);
|
|
83
|
+
pending.timer = setTimeout(() => onTimeout(pending), timeoutMs);
|
|
84
|
+
}
|
|
85
|
+
clearTimer(pending) {
|
|
86
|
+
if (pending.timer)
|
|
87
|
+
clearTimeout(pending.timer);
|
|
88
|
+
pending.timer = undefined;
|
|
89
|
+
}
|
|
90
|
+
/** Fully unregister a settled question (timer + both indexes). */
|
|
91
|
+
remove(pending) {
|
|
92
|
+
this.clearTimer(pending);
|
|
93
|
+
this.byKey.delete(pending.key);
|
|
94
|
+
if (this.byConversation.get(pending.conversationKey) === pending) {
|
|
95
|
+
this.byConversation.delete(pending.conversationKey);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=question-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question-state.js","sourceRoot":"","sources":["../../src/interactions/question-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure parser for free-text answers to a channel-presented Harness question.
|
|
3
|
+
*
|
|
4
|
+
* Non-button adapters (`interactiveActions` absent / false) still receive the
|
|
5
|
+
* question as numbered plain text; the user replies with a number, the exact
|
|
6
|
+
* option label, a multi-select index list, "跳过", or arbitrary custom text.
|
|
7
|
+
* This module turns that raw inbound text into a structured answer — nothing
|
|
8
|
+
* here touches adapters, backends, sessions or platform payloads, so it is a
|
|
9
|
+
* straightforward unit test target.
|
|
10
|
+
*
|
|
11
|
+
* Rules (no fuzzy matching on purpose — a free sentence must not be mistaken
|
|
12
|
+
* for an option):
|
|
13
|
+
*
|
|
14
|
+
* - single-select: `"1"` -> options[0]; exact label -> that option;
|
|
15
|
+
* `"跳过"` / `"跳过本题"` -> skip; any other non-empty text -> custom.
|
|
16
|
+
* - multi-select: `1,3`, `1,3` (full-width comma) and `1 3` (whitespace
|
|
17
|
+
* separated) select numbered options; a single exact label also counts.
|
|
18
|
+
* Labels are returned in OPTIONS order, deduplicated. Any index out of
|
|
19
|
+
* range -> `{ kind: 'invalid', reason: 'option-out-of-range' }`. Ordinary
|
|
20
|
+
* text that matches no legal option -> custom.
|
|
21
|
+
* - Empty text -> invalid.
|
|
22
|
+
*/
|
|
23
|
+
import type { AskUserQuestionItem } from '@deepseek-ai/dsh-user-questions/types';
|
|
24
|
+
export type ParsedQuestionTextAnswer = {
|
|
25
|
+
kind: 'skip';
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'selected';
|
|
28
|
+
labels: string[];
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'custom';
|
|
31
|
+
text: string;
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'invalid';
|
|
34
|
+
reason: string;
|
|
35
|
+
};
|
|
36
|
+
export declare function parseQuestionTextAnswer(question: AskUserQuestionItem, rawText: string): ParsedQuestionTextAnswer;
|
|
37
|
+
//# sourceMappingURL=question-text-answer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question-text-answer.d.ts","sourceRoot":"","sources":["../../src/interactions/question-text-answer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAEjF,MAAM,MAAM,wBAAwB,GAChC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAiDxC,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,MAAM,GACd,wBAAwB,CAwB1B"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const SKIP_WORDS = new Set(['跳过', '跳过本题']);
|
|
2
|
+
/**
|
|
3
|
+
* Split multi-select input into candidate index tokens. Accepts full-width
|
|
4
|
+
* commas and any run of whitespace/separators.
|
|
5
|
+
*/
|
|
6
|
+
function splitMultiSelectIndexes(rawText) {
|
|
7
|
+
// Normalise full-width comma + any comma/space runs into a single comma.
|
|
8
|
+
return rawText
|
|
9
|
+
.replace(/[\uFF0C]/g, ',')
|
|
10
|
+
.split(/[\s,]+/)
|
|
11
|
+
.map((token) => token.trim())
|
|
12
|
+
.filter((token) => token.length > 0);
|
|
13
|
+
}
|
|
14
|
+
function isNumeric(token) {
|
|
15
|
+
return /^\d+$/.test(token);
|
|
16
|
+
}
|
|
17
|
+
function parseMultiSelect(question, rawText) {
|
|
18
|
+
const options = question.options ?? [];
|
|
19
|
+
const selectedLabels = new Set();
|
|
20
|
+
const tokens = splitMultiSelectIndexes(rawText);
|
|
21
|
+
for (const token of tokens) {
|
|
22
|
+
if (!isNumeric(token)) {
|
|
23
|
+
// A single exact label is allowed as one selection; anything else falls
|
|
24
|
+
// through to custom below.
|
|
25
|
+
return { kind: 'custom', text: rawText };
|
|
26
|
+
}
|
|
27
|
+
const index = Number(token) - 1;
|
|
28
|
+
if (index < 0 || index >= options.length) {
|
|
29
|
+
return { kind: 'invalid', reason: 'option-out-of-range' };
|
|
30
|
+
}
|
|
31
|
+
selectedLabels.add(options[index].label);
|
|
32
|
+
}
|
|
33
|
+
if (selectedLabels.size === 0) {
|
|
34
|
+
return { kind: 'custom', text: rawText };
|
|
35
|
+
}
|
|
36
|
+
// Output in OPTIONS order, not user input order.
|
|
37
|
+
const ordered = options
|
|
38
|
+
.filter((option) => selectedLabels.has(option.label))
|
|
39
|
+
.map((option) => option.label);
|
|
40
|
+
return { kind: 'selected', labels: ordered };
|
|
41
|
+
}
|
|
42
|
+
export function parseQuestionTextAnswer(question, rawText) {
|
|
43
|
+
const text = rawText.trim();
|
|
44
|
+
if (text.length === 0)
|
|
45
|
+
return { kind: 'invalid', reason: 'empty' };
|
|
46
|
+
if (SKIP_WORDS.has(text))
|
|
47
|
+
return { kind: 'skip' };
|
|
48
|
+
if (question.multiSelect && (question.options?.length ?? 0) > 0) {
|
|
49
|
+
// Only route to multi-select parsing when options exist AND the input
|
|
50
|
+
// really looks like index input. Single exact labels still work here.
|
|
51
|
+
if (question.options?.some((option) => option.label === text)) {
|
|
52
|
+
return { kind: 'selected', labels: [text] };
|
|
53
|
+
}
|
|
54
|
+
const multi = parseMultiSelect(question, text);
|
|
55
|
+
// parseMultiSelect already returns custom for non-numeric text, so a
|
|
56
|
+
// plain-text label that passed the exact match above is the only way in
|
|
57
|
+
// that doesn't already return a valid answer.
|
|
58
|
+
return multi;
|
|
59
|
+
}
|
|
60
|
+
const options = question.options ?? [];
|
|
61
|
+
const numeric = /^\d+$/.test(text) ? Number(text) - 1 : -1;
|
|
62
|
+
if (numeric >= 0 && numeric < options.length) {
|
|
63
|
+
return { kind: 'selected', labels: [options[numeric].label] };
|
|
64
|
+
}
|
|
65
|
+
const match = options.find((option) => option.label === text);
|
|
66
|
+
if (match)
|
|
67
|
+
return { kind: 'selected', labels: [match.label] };
|
|
68
|
+
return { kind: 'custom', text };
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=question-text-answer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question-text-answer.js","sourceRoot":"","sources":["../../src/interactions/question-text-answer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { type UserQuestionService } from '@deepseek-ai/dsh-user-questions';
|
|
3
|
+
import type { ChannelLogger } from '@krischoichoi/channel-core';
|
|
4
|
+
import type { QuestionInteractionBackend, QuestionInteractionCancellation, QuestionInteractionResolution, QuestionInteractionSink } from './question-backend.js';
|
|
5
|
+
/**
|
|
6
|
+
* Narrow view over the official `ctx.userQuestions` service — the channel
|
|
7
|
+
* only probes its presence; asks are received via the waterfall event.
|
|
8
|
+
*/
|
|
9
|
+
export type ChannelUserQuestionService = UserQuestionService;
|
|
10
|
+
export interface WaterfallQuestionBackendOptions {
|
|
11
|
+
/** Root Cordis context the `user-questions/request` listener registers on. */
|
|
12
|
+
ctx: Context;
|
|
13
|
+
logger: ChannelLogger;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The channel's answerer on the official question waterfall. One listener
|
|
17
|
+
* instance serves every Agent (untagged root registration receives all
|
|
18
|
+
* agent-scoped dispatches); requests the channel cannot present are
|
|
19
|
+
* delegated to the next answerer via `next()`.
|
|
20
|
+
*/
|
|
21
|
+
export declare class WaterfallQuestionBackend implements QuestionInteractionBackend {
|
|
22
|
+
private readonly options;
|
|
23
|
+
readonly kind: "waterfall";
|
|
24
|
+
private sink?;
|
|
25
|
+
private readonly pending;
|
|
26
|
+
private disposeListener?;
|
|
27
|
+
constructor(options: WaterfallQuestionBackendOptions);
|
|
28
|
+
start(sink: QuestionInteractionSink): void;
|
|
29
|
+
stop(): Promise<void>;
|
|
30
|
+
resolve(submission: QuestionInteractionResolution): Promise<void>;
|
|
31
|
+
cancel(cancellation: QuestionInteractionCancellation): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* One official waterfall ask. The channel claims the request by presenting
|
|
34
|
+
* it on the bound conversation and parking the answer promise; a declined
|
|
35
|
+
* presentation (no bound conversation, one already pending there, adapter
|
|
36
|
+
* absent / text unsupported) delegates to the next answerer.
|
|
37
|
+
*/
|
|
38
|
+
private answer;
|
|
39
|
+
/** Detach the abort listener and run the settlement exactly once. */
|
|
40
|
+
private settle;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=question-waterfall-backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question-waterfall-backend.d.ts","sourceRoot":"","sources":["../../src/interactions/question-waterfall-backend.ts"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EACV,0BAA0B,EAC1B,+BAA+B,EAC/B,6BAA6B,EAC7B,uBAAuB,EACxB,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,MAAM,MAAM,0BAA0B,GAAG,mBAAmB,CAAC;AAU7D,MAAM,WAAW,+BAA+B;IAC9C,8EAA8E;IAC9E,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;CACvB;AAED;;;;;GAKG;AACH,qBAAa,wBAAyB,YAAW,0BAA0B;IAO7D,OAAO,CAAC,QAAQ,CAAC,OAAO;IANpC,QAAQ,CAAC,IAAI,EAAG,WAAW,CAAU;IAErC,OAAO,CAAC,IAAI,CAAC,CAA0B;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,eAAe,CAAC,CAAa;gBAER,OAAO,EAAE,+BAA+B;IAErE,KAAK,CAAC,IAAI,EAAE,uBAAuB,GAAG,IAAI;IAapC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAarB,OAAO,CAAC,UAAU,EAAE,6BAA6B,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjE,MAAM,CAAC,YAAY,EAAE,+BAA+B,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ1E;;;;;OAKG;YACW,MAAM;IAyDpB,qEAAqE;IACrE,OAAO,CAAC,MAAM;CASf"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified question backend: the official `user-questions/request` waterfall.
|
|
3
|
+
*
|
|
4
|
+
* The question domain (`ask_user_question` -> `ctx.userQuestions.ask`)
|
|
5
|
+
* dispatches a Cordis waterfall event: every interested surface composes an
|
|
6
|
+
* answerer listener, claims the request by returning an answer, or delegates
|
|
7
|
+
* with `next()` (the official service rejects with `UserQuestionError`
|
|
8
|
+
* `NO_PROVIDER` when the whole waterfall delegates). Scope-filtered dispatch
|
|
9
|
+
* (`@deepseek-ai/dsh-scope`) admits untagged listeners globally, so ONE
|
|
10
|
+
* root-level listener serves every profile:
|
|
11
|
+
*
|
|
12
|
+
* - headless: the channel is typically the only answerer, so a declined
|
|
13
|
+
* presentation fails the ask ("decline -> next() -> NO_PROVIDER");
|
|
14
|
+
* - web profile: the official Web UI composes its own answerer on the same
|
|
15
|
+
* waterfall, so a channel decline DELEGATES to it.
|
|
16
|
+
*
|
|
17
|
+
* The listener is registered on the ROOT context in `start()`; an abort or
|
|
18
|
+
* teardown rejects the ask with `UserQuestionError` (`ASK_ABORTED`).
|
|
19
|
+
*
|
|
20
|
+
* ## Why the listener is PREPENDED (ordering IS a contract)
|
|
21
|
+
*
|
|
22
|
+
* The scoped dispatch admits untagged root listeners, but a Cordis waterfall
|
|
23
|
+
* is SEQUENTIAL: the first listener that returns an answer vetoes every later
|
|
24
|
+
* one. The shipped `web` profile composes the official Remote/Web answerer
|
|
25
|
+
* (`@deepseek-ai/dsh-api-remotes`) on this same event, and that fiber applies
|
|
26
|
+
* at boot — long before the bridge (it only waits for `typertGateway`, while
|
|
27
|
+
* the bridge waits for `channels`) — so a plain `ctx.on()` registration loses
|
|
28
|
+
* every channel-bound ask to the browser:
|
|
29
|
+
*
|
|
30
|
+
* - a connected client materializes the Agent scope for ANY forwarded ask and
|
|
31
|
+
* holds it until the human answers in the Web UI (`dsh-client-ui-user-questions`
|
|
32
|
+
* claims whenever `sessions.scopeOf(owner)` resolves), so the channel never
|
|
33
|
+
* sees the request;
|
|
34
|
+
* - with no client connected the forwarded request is parked in the gateway
|
|
35
|
+
* (`pendingRemoteEvents`) with nobody to deliver it to and no auto-`next()`,
|
|
36
|
+
* so the channel still never sees it.
|
|
37
|
+
*
|
|
38
|
+
* Prepending inverts that: a channel-bound conversation claims its own ask
|
|
39
|
+
* (buttons where the platform supports them, numbered-text fallback
|
|
40
|
+
* otherwise), and every ask the channel cannot present — no binding, no
|
|
41
|
+
* active reply context, unsupported text, or one already pending on that
|
|
42
|
+
* conversation — still delegates through `next()` to the Web answerer.
|
|
43
|
+
*/
|
|
44
|
+
import { randomUUID } from 'node:crypto';
|
|
45
|
+
import { UserQuestionError, } from '@deepseek-ai/dsh-user-questions';
|
|
46
|
+
/**
|
|
47
|
+
* The channel's answerer on the official question waterfall. One listener
|
|
48
|
+
* instance serves every Agent (untagged root registration receives all
|
|
49
|
+
* agent-scoped dispatches); requests the channel cannot present are
|
|
50
|
+
* delegated to the next answerer via `next()`.
|
|
51
|
+
*/
|
|
52
|
+
export class WaterfallQuestionBackend {
|
|
53
|
+
options;
|
|
54
|
+
kind = 'waterfall';
|
|
55
|
+
sink;
|
|
56
|
+
pending = new Map();
|
|
57
|
+
disposeListener;
|
|
58
|
+
constructor(options) {
|
|
59
|
+
this.options = options;
|
|
60
|
+
}
|
|
61
|
+
start(sink) {
|
|
62
|
+
if (this.disposeListener)
|
|
63
|
+
return;
|
|
64
|
+
this.sink = sink;
|
|
65
|
+
// `prepend: true` — see the class header: the official Remote/Web
|
|
66
|
+
// answerer registers first in the shipped web profile and would
|
|
67
|
+
// otherwise veto every channel-bound ask.
|
|
68
|
+
this.disposeListener = this.options.ctx.on('user-questions/request', (request, next) => this.answer(request, next), { prepend: true });
|
|
69
|
+
}
|
|
70
|
+
async stop() {
|
|
71
|
+
this.sink = undefined;
|
|
72
|
+
this.disposeListener?.();
|
|
73
|
+
this.disposeListener = undefined;
|
|
74
|
+
for (const pending of [...this.pending.values()]) {
|
|
75
|
+
this.settle(pending, () => {
|
|
76
|
+
pending.reject(new UserQuestionError('channel user-questions answerer was disposed', 'ASK_ABORTED'));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async resolve(submission) {
|
|
81
|
+
const pending = this.pending.get(submission.key);
|
|
82
|
+
if (!pending)
|
|
83
|
+
return;
|
|
84
|
+
this.settle(pending, () => pending.resolve(submission.answer));
|
|
85
|
+
}
|
|
86
|
+
async cancel(cancellation) {
|
|
87
|
+
const pending = this.pending.get(cancellation.key);
|
|
88
|
+
if (!pending)
|
|
89
|
+
return;
|
|
90
|
+
this.settle(pending, () => {
|
|
91
|
+
pending.reject(new UserQuestionError(cancellation.reason, 'ASK_ABORTED'));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* One official waterfall ask. The channel claims the request by presenting
|
|
96
|
+
* it on the bound conversation and parking the answer promise; a declined
|
|
97
|
+
* presentation (no bound conversation, one already pending there, adapter
|
|
98
|
+
* absent / text unsupported) delegates to the next answerer.
|
|
99
|
+
*/
|
|
100
|
+
async answer(request, next) {
|
|
101
|
+
const sink = this.sink;
|
|
102
|
+
// An ask without its owning live agent has no session to bind to a
|
|
103
|
+
// channel conversation — never the channel's business.
|
|
104
|
+
if (!sink || request.agent === undefined)
|
|
105
|
+
return next();
|
|
106
|
+
const sessionId = String(request.agent.id);
|
|
107
|
+
const key = `waterfall-${randomUUID()}`;
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const pending = {
|
|
110
|
+
key,
|
|
111
|
+
resolve,
|
|
112
|
+
reject,
|
|
113
|
+
...(request.signal === undefined ? {} : { signal: request.signal }),
|
|
114
|
+
};
|
|
115
|
+
const onAbort = () => {
|
|
116
|
+
this.settle(pending, () => {
|
|
117
|
+
reject(new UserQuestionError('ask_user_question was aborted before the user answered', 'ASK_ABORTED'));
|
|
118
|
+
});
|
|
119
|
+
// Drop the channel-side presentation (buttons/state) after the ask
|
|
120
|
+
// itself settled; a racing channel answer becomes a backend no-op.
|
|
121
|
+
void sink.questionSettledExternally(key, 'aborted');
|
|
122
|
+
};
|
|
123
|
+
pending.onAbort = onAbort;
|
|
124
|
+
request.signal?.addEventListener('abort', onAbort, { once: true });
|
|
125
|
+
// Park the pending entry BEFORE presenting: the presenter hands the key
|
|
126
|
+
// to channel interactions, which resolve through `resolve()`/`cancel()`.
|
|
127
|
+
this.pending.set(key, pending);
|
|
128
|
+
void sink
|
|
129
|
+
.questionRequested({ key, sessionId, questions: request.questions })
|
|
130
|
+
.then(async (accepted) => {
|
|
131
|
+
if (accepted || !this.pending.has(key))
|
|
132
|
+
return;
|
|
133
|
+
// The channel declined ownership — remove the parked entry and let
|
|
134
|
+
// the next answerer (e.g. the official Web UI) take the request.
|
|
135
|
+
this.settle(pending, () => { });
|
|
136
|
+
resolve(await next());
|
|
137
|
+
})
|
|
138
|
+
.catch((error) => {
|
|
139
|
+
if (this.pending.has(key)) {
|
|
140
|
+
this.settle(pending, () => reject(error));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Decline-path `next()` failure (e.g. NO_PROVIDER when no further
|
|
144
|
+
// answerer claims): the parked entry is already gone, so the ask
|
|
145
|
+
// promise itself must reject instead of hanging forever.
|
|
146
|
+
reject(error);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/** Detach the abort listener and run the settlement exactly once. */
|
|
151
|
+
settle(pending, settle) {
|
|
152
|
+
if (!this.pending.has(pending.key))
|
|
153
|
+
return;
|
|
154
|
+
this.pending.delete(pending.key);
|
|
155
|
+
if (pending.signal && pending.onAbort) {
|
|
156
|
+
pending.signal.removeEventListener('abort', pending.onAbort);
|
|
157
|
+
}
|
|
158
|
+
pending.onAbort = undefined;
|
|
159
|
+
settle();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=question-waterfall-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question-waterfall-backend.js","sourceRoot":"","sources":["../../src/interactions/question-waterfall-backend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge lifecycle assembly (doc §4 / §40).
|
|
3
|
+
*
|
|
4
|
+
* `startBridge(ctx, config, persistence?)` wires everything together and
|
|
5
|
+
* returns a disposer with the mandated stop order (doc 4.3):
|
|
6
|
+
* 1. stop new inbound (unregister the ChannelService listener);
|
|
7
|
+
* 2. drain active turns (`whenIdle`, bounded by a timeout);
|
|
8
|
+
* 3. RECONCILE replies from the Session durable log — final-reply correctness
|
|
9
|
+
* comes from the (durable) Session log, not from the `session/event`
|
|
10
|
+
* listener still being attached;
|
|
11
|
+
* 4. flush any replies still marked active (`ReplyRouter.flushAll`);
|
|
12
|
+
* 5. stop listening to `session/event`;
|
|
13
|
+
* 6. dispose all owned agent handles;
|
|
14
|
+
* 7. dispose the ReplyRouter (clear timers).
|
|
15
|
+
*
|
|
16
|
+
* Persistence is an OPTIONAL capability resolved LIVE at the use site: the
|
|
17
|
+
* caller passes a resolver (`() => ctx.get('sessionPersistence')`), and the
|
|
18
|
+
* gateway's `canResume()` / probes reflect whether it is present RIGHT NOW —
|
|
19
|
+
* mounting, unmounting, or replacing the service after startup is observed on
|
|
20
|
+
* the next probe (never a startup snapshot).
|
|
21
|
+
*
|
|
22
|
+
* Two reply-context correlation listeners (`agent/inbox/claimed` and
|
|
23
|
+
* `agent/inbox/discarded`) are registered on the Cordis context; they are
|
|
24
|
+
* plain `ctx.on`, so they auto-dispose with the context and stay active for
|
|
25
|
+
* the whole bridge lifetime.
|
|
26
|
+
*/
|
|
27
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
28
|
+
import type { SessionPersistence } from '@deepseek-ai/dsh-session-persistence';
|
|
29
|
+
import type { ChannelEvent } from '@krischoichoi/channel-core';
|
|
30
|
+
import type { Config } from './config.js';
|
|
31
|
+
export interface BridgeLifecycle {
|
|
32
|
+
dispose(): Promise<void>;
|
|
33
|
+
handleChannelEvent(event: ChannelEvent): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export declare function startBridge(ctx: Context, config: Config, resolvePersistence?: () => SessionPersistence | undefined): BridgeLifecycle;
|
|
36
|
+
//# sourceMappingURL=lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAE/E,OAAO,KAAK,EAAkB,YAAY,EAAgD,MAAM,4BAA4B,CAAC;AAC7H,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAsB1C,MAAM,WAAW,eAAe;IAC9B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxD;AAED,wBAAgB,WAAW,CACzB,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,kBAAkB,CAAC,EAAE,MAAM,kBAAkB,GAAG,SAAS,GACxD,eAAe,CAgOjB"}
|