@things-factory/ai-assistant 10.1.26 → 10.1.28
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/client/components/assistant-chat.ts +653 -59
- package/client/components/assistant-mode.test.ts +86 -55
- package/client/components/assistant-mode.ts +124 -82
- package/client/components/chat-echo-dedup.test.ts +23 -0
- package/client/components/chat-echo-dedup.ts +29 -3
- package/client/components/chat-input-builder.test.ts +20 -20
- package/client/components/chat-input-builder.ts +16 -14
- package/client/components/chat-proposal-survival.test.ts +73 -0
- package/client/components/mention-popup.ts +31 -8
- package/client/components/panel-aria.test.ts +34 -0
- package/client/components/proposal-outcome-line.ts +33 -0
- package/client/utils/assistant-session-controller.test.ts +75 -0
- package/client/utils/assistant-session-controller.ts +23 -0
- package/client/utils/assistant-session-transport.ts +11 -3
- package/dist-client/components/assistant-chat.d.ts +128 -13
- package/dist-client/components/assistant-chat.js +613 -67
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/components/assistant-mode.d.ts +80 -68
- package/dist-client/components/assistant-mode.js +63 -19
- package/dist-client/components/assistant-mode.js.map +1 -1
- package/dist-client/components/assistant-mode.test.js +74 -53
- package/dist-client/components/assistant-mode.test.js.map +1 -1
- package/dist-client/components/chat-echo-dedup.d.ts +2 -0
- package/dist-client/components/chat-echo-dedup.js +27 -4
- package/dist-client/components/chat-echo-dedup.js.map +1 -1
- package/dist-client/components/chat-echo-dedup.test.js +20 -0
- package/dist-client/components/chat-echo-dedup.test.js.map +1 -1
- package/dist-client/components/chat-input-builder.d.ts +12 -7
- package/dist-client/components/chat-input-builder.js +8 -10
- package/dist-client/components/chat-input-builder.js.map +1 -1
- package/dist-client/components/chat-input-builder.test.js +17 -18
- package/dist-client/components/chat-input-builder.test.js.map +1 -1
- package/dist-client/components/chat-proposal-survival.test.d.ts +1 -0
- package/dist-client/components/chat-proposal-survival.test.js +61 -0
- package/dist-client/components/chat-proposal-survival.test.js.map +1 -0
- package/dist-client/components/mention-popup.js +28 -8
- package/dist-client/components/mention-popup.js.map +1 -1
- package/dist-client/components/panel-aria.test.d.ts +1 -0
- package/dist-client/components/panel-aria.test.js +31 -0
- package/dist-client/components/panel-aria.test.js.map +1 -0
- package/dist-client/components/proposal-outcome-line.d.ts +9 -0
- package/dist-client/components/proposal-outcome-line.js +15 -0
- package/dist-client/components/proposal-outcome-line.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/assistant-session-controller.d.ts +10 -0
- package/dist-client/utils/assistant-session-controller.js +16 -0
- package/dist-client/utils/assistant-session-controller.js.map +1 -1
- package/dist-client/utils/assistant-session-controller.test.d.ts +1 -0
- package/dist-client/utils/assistant-session-controller.test.js +56 -0
- package/dist-client/utils/assistant-session-controller.test.js.map +1 -0
- package/dist-client/utils/assistant-session-transport.js +11 -3
- package/dist-client/utils/assistant-session-transport.js.map +1 -1
- package/dist-server/service/assistant-capability-resolver.d.ts +17 -0
- package/dist-server/service/assistant-capability-resolver.js +88 -0
- package/dist-server/service/assistant-capability-resolver.js.map +1 -0
- package/dist-server/service/assistant-chat-resolver.d.ts +6 -6
- package/dist-server/service/assistant-chat-resolver.js +166 -44
- package/dist-server/service/assistant-chat-resolver.js.map +1 -1
- package/dist-server/service/assistant-mode/assistant-mode-resolver.d.ts +25 -0
- package/dist-server/service/assistant-mode/assistant-mode-resolver.js +140 -0
- package/dist-server/service/assistant-mode/assistant-mode-resolver.js.map +1 -0
- package/dist-server/service/assistant-mode/index.d.ts +9 -0
- package/dist-server/service/assistant-mode/index.js +19 -0
- package/dist-server/service/assistant-mode/index.js.map +1 -0
- package/dist-server/service/assistant-mode/registry.d.ts +18 -0
- package/dist-server/service/assistant-mode/registry.js +136 -0
- package/dist-server/service/assistant-mode/registry.js.map +1 -0
- package/dist-server/service/assistant-mode/resolve.d.ts +33 -0
- package/dist-server/service/assistant-mode/resolve.js +60 -0
- package/dist-server/service/assistant-mode/resolve.js.map +1 -0
- package/dist-server/service/assistant-mode/types.d.ts +99 -0
- package/dist-server/service/assistant-mode/types.js +12 -0
- package/dist-server/service/assistant-mode/types.js.map +1 -0
- package/dist-server/service/chat-message/carried-turns.d.ts +48 -0
- package/dist-server/service/chat-message/carried-turns.js +80 -0
- package/dist-server/service/chat-message/carried-turns.js.map +1 -0
- package/dist-server/service/chat-message/chat-message-subscription.d.ts +1 -1
- package/dist-server/service/chat-message/chat-message-subscription.js +24 -6
- package/dist-server/service/chat-message/chat-message-subscription.js.map +1 -1
- package/dist-server/service/chat-message/chat-message.d.ts +32 -0
- package/dist-server/service/chat-message/chat-message.js +17 -0
- package/dist-server/service/chat-message/chat-message.js.map +1 -1
- package/dist-server/service/chat-message/door-access.d.ts +15 -0
- package/dist-server/service/chat-message/door-access.js +26 -0
- package/dist-server/service/chat-message/door-access.js.map +1 -0
- package/dist-server/service/chat-message/fold-session-history.d.ts +30 -0
- package/dist-server/service/chat-message/fold-session-history.js +133 -0
- package/dist-server/service/chat-message/fold-session-history.js.map +1 -0
- package/dist-server/service/chat-message/llm-history.d.ts +7 -0
- package/dist-server/service/chat-message/llm-history.js +23 -11
- package/dist-server/service/chat-message/llm-history.js.map +1 -1
- package/dist-server/service/chat-message/message-doors.d.ts +29 -0
- package/dist-server/service/chat-message/message-doors.js +107 -0
- package/dist-server/service/chat-message/message-doors.js.map +1 -0
- package/dist-server/service/chat-message/proposal-outcome.d.ts +18 -0
- package/dist-server/service/chat-message/proposal-outcome.js +31 -0
- package/dist-server/service/chat-message/proposal-outcome.js.map +1 -0
- package/dist-server/service/chat-message/reader-gate.d.ts +3 -0
- package/dist-server/service/chat-message/reader-gate.js +37 -0
- package/dist-server/service/chat-message/reader-gate.js.map +1 -0
- package/dist-server/service/chat-message/room-message.d.ts +4 -0
- package/dist-server/service/chat-message/room-message.js +25 -0
- package/dist-server/service/chat-message/room-message.js.map +1 -0
- package/dist-server/service/chat-session/chat-session.d.ts +24 -0
- package/dist-server/service/chat-session/chat-session.js +26 -2
- package/dist-server/service/chat-session/chat-session.js.map +1 -1
- package/dist-server/service/chat-session/session-inbox.d.ts +4 -1
- package/dist-server/service/chat-session/session-inbox.js +3 -2
- package/dist-server/service/chat-session/session-inbox.js.map +1 -1
- package/dist-server/service/chat-session/session-visibility.d.ts +15 -0
- package/dist-server/service/chat-session/session-visibility.js +28 -0
- package/dist-server/service/chat-session/session-visibility.js.map +1 -0
- package/dist-server/service/chat-session/station-session-guard.d.ts +1 -5
- package/dist-server/service/chat-session/station-session-guard.js +7 -2
- package/dist-server/service/chat-session/station-session-guard.js.map +1 -1
- package/dist-server/service/chat-session-participant/mention-invite.d.ts +29 -0
- package/dist-server/service/chat-session-participant/mention-invite.js +103 -0
- package/dist-server/service/chat-session-participant/mention-invite.js.map +1 -0
- package/dist-server/service/chat-session-resolver.d.ts +15 -0
- package/dist-server/service/chat-session-resolver.js +267 -15
- package/dist-server/service/chat-session-resolver.js.map +1 -1
- package/dist-server/service/grounding-sources.d.ts +14 -0
- package/dist-server/service/grounding-sources.js +26 -0
- package/dist-server/service/grounding-sources.js.map +1 -0
- package/dist-server/service/index.d.ts +8 -1
- package/dist-server/service/index.js +19 -0
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/request-translator.d.ts +2 -0
- package/dist-server/service/request-translator.js +20 -0
- package/dist-server/service/request-translator.js.map +1 -0
- package/dist-server/service/testing/conversation-script.d.ts +123 -0
- package/dist-server/service/testing/conversation-script.js +202 -0
- package/dist-server/service/testing/conversation-script.js.map +1 -0
- package/dist-server/service/testing/mode-conformance.d.ts +40 -0
- package/dist-server/service/testing/mode-conformance.js +90 -0
- package/dist-server/service/testing/mode-conformance.js.map +1 -0
- package/dist-server/tool-registry-boot-report.d.ts +3 -0
- package/dist-server/tool-registry-boot-report.js +25 -2
- package/dist-server/tool-registry-boot-report.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/server/explicit-graphql-types.test.ts +56 -0
- package/server/service/assistant-capability-resolver.ts +66 -0
- package/server/service/assistant-chat-resolver.ts +184 -42
- package/server/service/assistant-mode/assistant-mode-resolver.ts +102 -0
- package/server/service/assistant-mode/index.ts +16 -0
- package/server/service/assistant-mode/registry.test.ts +105 -0
- package/server/service/assistant-mode/registry.ts +147 -0
- package/server/service/assistant-mode/resolve.test.ts +78 -0
- package/server/service/assistant-mode/resolve.ts +96 -0
- package/server/service/assistant-mode/subject-defaults.test.ts +86 -0
- package/server/service/assistant-mode/types.ts +103 -0
- package/server/service/chat-message/carried-turns.test.ts +69 -0
- package/server/service/chat-message/carried-turns.ts +99 -0
- package/server/service/chat-message/chat-message-subscription.ts +20 -4
- package/server/service/chat-message/chat-message.ts +44 -0
- package/server/service/chat-message/door-access.ts +45 -0
- package/server/service/chat-message/fold-session-history.test.ts +98 -0
- package/server/service/chat-message/fold-session-history.ts +115 -0
- package/server/service/chat-message/llm-history.test.ts +58 -7
- package/server/service/chat-message/llm-history.ts +30 -11
- package/server/service/chat-message/message-doors.test.ts +87 -0
- package/server/service/chat-message/message-doors.ts +117 -0
- package/server/service/chat-message/proposal-outcome.test.ts +48 -0
- package/server/service/chat-message/proposal-outcome.ts +42 -0
- package/server/service/chat-message/reader-gate.ts +42 -0
- package/server/service/chat-message/room-message.test.ts +21 -0
- package/server/service/chat-message/room-message.ts +22 -0
- package/server/service/chat-session/chat-session.ts +31 -1
- package/server/service/chat-session/session-inbox.test.ts +18 -0
- package/server/service/chat-session/session-inbox.ts +9 -2
- package/server/service/chat-session/session-visibility.test.ts +36 -0
- package/server/service/chat-session/session-visibility.ts +26 -0
- package/server/service/chat-session/station-session-guard.ts +10 -2
- package/server/service/chat-session-participant/mention-invite.test.ts +125 -0
- package/server/service/chat-session-participant/mention-invite.ts +121 -0
- package/server/service/chat-session-resolver.ts +272 -15
- package/server/service/grounding-sources.test.ts +55 -0
- package/server/service/grounding-sources.ts +46 -0
- package/server/service/index.ts +19 -0
- package/server/service/request-translator.test.ts +45 -0
- package/server/service/request-translator.ts +33 -0
- package/server/service/station-session-guard.test.ts +12 -5
- package/server/service/testing/conversation-scenario.test.ts +163 -0
- package/server/service/testing/conversation-script.test.ts +131 -0
- package/server/service/testing/conversation-script.ts +288 -0
- package/server/service/testing/mode-conformance.ts +107 -0
- package/server/tool-registry-boot-report.ts +25 -2
- package/test/assistant-chat-call-sites.test.ts +82 -0
- package/translations/en.json +44 -2
- package/translations/ja.json +44 -2
- package/translations/ko.json +44 -2
- package/translations/ms.json +44 -2
- package/translations/zh.json +44 -2
|
@@ -1,97 +1,109 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A mode is what a conversation *is*:
|
|
3
|
-
* about, what it offers to start with, and when it is over.
|
|
2
|
+
* A mode is what a conversation *is*: which conversation this is, and what it is about.
|
|
4
3
|
*
|
|
5
|
-
* Those
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* So a host declares one value and hands it over. What cannot be split cannot fall out of step.
|
|
4
|
+
* Those two used to be nine. The browser declared the persona, the tool categories, the model and
|
|
5
|
+
* the limits and sent them with every turn, which meant a caller holding the door's privilege
|
|
6
|
+
* chose its own authority — and it meant a screen could describe a conversation one way while the
|
|
7
|
+
* server answered it another. Since ADR-0061 decision 7 the app server registers the mode and the
|
|
8
|
+
* browser names it:
|
|
11
9
|
*
|
|
12
10
|
* ```ts
|
|
13
|
-
*
|
|
14
|
-
* id: `twin:space:${spaceId}`,
|
|
15
|
-
* systemPrompt: '...',
|
|
16
|
-
* toolCategories: ['twin'],
|
|
17
|
-
* hostContext: { spaceId },
|
|
18
|
-
* slashTemplates: TWIN_SLASH_TEMPLATES
|
|
19
|
-
* })
|
|
20
|
-
*
|
|
21
|
-
* html`<ox-assistant-chat .mode=${SPACE_MODE(this.spaceId)}></ox-assistant-chat>`
|
|
11
|
+
* html`<ox-assistant-chat .mode=${{ kind: 'twin.space', target: spaceId, hostContext: { spaceId } }}></ox-assistant-chat>`
|
|
22
12
|
* ```
|
|
13
|
+
*
|
|
14
|
+
* What the panel shows — the `/` sentences, the `@` catalog, the two notices — comes from the same
|
|
15
|
+
* declaration, fetched from the server already written in the person's language, so the panel and
|
|
16
|
+
* the door cannot offer different conversations.
|
|
23
17
|
*/
|
|
24
18
|
export interface AssistantMode {
|
|
25
19
|
/**
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*
|
|
31
|
-
* they are no longer looking at.
|
|
32
|
-
*
|
|
33
|
-
* Hosts built this by hand before, each in their own way: plant kept a module-global registry
|
|
34
|
-
* with subscribers and re-checked the context on every send, twin closed its session when the
|
|
35
|
-
* anchor moved. Both were writing the same guard around the same absence.
|
|
20
|
+
* Which conversation this is — a `<app>.<surface>` kind the app server registered.
|
|
21
|
+
*/
|
|
22
|
+
kind: string;
|
|
23
|
+
/**
|
|
24
|
+
* What it is about: the space, the station, the model being edited.
|
|
36
25
|
*
|
|
37
|
-
*
|
|
26
|
+
* **With `kind`, this is the conversation's lifetime.** When either changes the chat closes what
|
|
27
|
+
* is on screen and starts again — a conversation about station 3 is not a conversation about
|
|
28
|
+
* station 7, and leaving the old one up means the person reads an answer about a screen they are
|
|
29
|
+
* no longer looking at.
|
|
38
30
|
*/
|
|
39
|
-
|
|
40
|
-
/** Who the assistant is here, and what it must not claim to do. */
|
|
41
|
-
systemPrompt?: string;
|
|
42
|
-
/** Which tool categories the server may expose for this conversation. */
|
|
43
|
-
toolCategories?: string[];
|
|
44
|
-
/** What the host knows about the subject; travels with each request. */
|
|
45
|
-
hostContext?: any;
|
|
46
|
-
/** Which server-side chat entry point answers. */
|
|
47
|
-
chatEndpoint?: 'boardAIChat' | 'assistantChat';
|
|
48
|
-
/** What `@` offers. */
|
|
49
|
-
catalogEntries?: any[];
|
|
31
|
+
target?: string;
|
|
50
32
|
/**
|
|
51
|
-
* What
|
|
33
|
+
* What the host knows about the subject; travels with each request.
|
|
52
34
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* cannot do**: everything here should be answerable by the tools `toolCategories` opens.
|
|
35
|
+
* **The model never sees this** — it reaches the tools as `ctx.host` and nothing else (ADR-0061
|
|
36
|
+
* decision 7). Anything the model must know is answered by a read tool.
|
|
56
37
|
*/
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
38
|
+
hostContext?: any;
|
|
39
|
+
}
|
|
40
|
+
/** What the server said this mode offers, already in the request's locale. */
|
|
41
|
+
export interface AssistantModeView {
|
|
42
|
+
kind: string;
|
|
43
|
+
slashTemplates: any[];
|
|
44
|
+
catalogEntries: any[];
|
|
45
|
+
proposalChoices: any[];
|
|
46
|
+
placeholder?: string;
|
|
47
|
+
footerNotice?: string;
|
|
48
|
+
stagedNotice?: string;
|
|
64
49
|
}
|
|
65
50
|
/** True when these two describe different conversations, so the old one should be closed. */
|
|
66
51
|
export declare function isDifferentConversation(a?: AssistantMode, b?: AssistantMode): boolean;
|
|
67
|
-
/** Everything a conversation needs, after the mode, the host's own properties
|
|
68
|
-
*
|
|
52
|
+
/** Everything a conversation needs, after the mode, the host's own properties, the registered
|
|
53
|
+
* defaults and the server's answer have been reconciled. */
|
|
69
54
|
export interface ConversationSettings {
|
|
70
|
-
|
|
71
|
-
|
|
55
|
+
/** Set when a mode names this conversation; absent for a host that declares none. */
|
|
56
|
+
kind?: string;
|
|
57
|
+
target?: string;
|
|
72
58
|
hostContext?: any;
|
|
73
59
|
chatEndpoint: 'boardAIChat' | 'assistantChat';
|
|
74
60
|
catalogEntries: any[];
|
|
75
61
|
slashTemplates: any[];
|
|
76
|
-
|
|
62
|
+
/** What the empty input says. A mode's comes translated from the server. */
|
|
63
|
+
placeholder?: string;
|
|
64
|
+
/** Sentences, not keys — a mode's come translated from the server, a host's from its own i18n. */
|
|
65
|
+
footerNotice?: string;
|
|
77
66
|
proposalChoices: any[];
|
|
78
|
-
|
|
67
|
+
stagedNotice?: string;
|
|
79
68
|
}
|
|
80
69
|
/**
|
|
81
70
|
* Reconcile the places a setting can come from.
|
|
82
71
|
*
|
|
83
|
-
* **A host that declares a mode gets the mode and nothing else.**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
72
|
+
* **A host that declares a mode gets the mode and nothing else.** What the mode does not offer is
|
|
73
|
+
* not filled in from the element's own properties or from the defaults another package registered.
|
|
74
|
+
* Filling it back in is how the defect this type removes returned — board-ai registers board
|
|
75
|
+
* authoring's `/add-monitor`, its component `@` catalog and its "previewed on the board" line
|
|
76
|
+
* globally, and plant's factory mode, which declares no slash list, offered them on a factory
|
|
77
|
+
* screen once the board modeller had been loaded.
|
|
78
|
+
*
|
|
79
|
+
* A mode's offering arrives asynchronously, so until the server has answered a mode offers
|
|
80
|
+
* nothing. Standing the defaults in during that gap would show the board's list for a moment on a
|
|
81
|
+
* screen that does not edit boards.
|
|
82
|
+
*
|
|
83
|
+
* A host that declares no mode gets exactly what it got before modes existed — its own properties,
|
|
84
|
+
* falling back to the registered defaults — and it must name its own door: the `boardAIChat`
|
|
85
|
+
* default was removed with decision 7, because a missing door should show when a surface is
|
|
86
|
+
* declared, not when a person says something.
|
|
87
|
+
*/
|
|
88
|
+
export declare function resolveConversation(mode: AssistantMode | undefined, own: Partial<ConversationSettings>, defaults: Partial<ConversationSettings>, view?: AssistantModeView): ConversationSettings;
|
|
89
|
+
/** 한 subject 위에 설 수 있는 대화 하나 — 서버가 준 목록의 한 줄. */
|
|
90
|
+
export interface AssistantModeChoice {
|
|
91
|
+
kind: string;
|
|
92
|
+
label: string;
|
|
93
|
+
isDefault: boolean;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 이 화면을 열 때 어느 대화로 설 것인가 — ADR-0072 A17 ①.
|
|
97
|
+
*
|
|
98
|
+
* ── 왜 기억하나 ───────────────────────────────────────────────────────────
|
|
99
|
+
* 보드를 열 때마다 저작 대화로 되돌아가면, 현장을 물으려고 온 사람은 화면을 열 때마다 같은 손질을
|
|
100
|
+
* 다시 한다. 기억은 이 브라우저에만 남는다 — 서버에 칸을 만들지 않는다.
|
|
90
101
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
102
|
+
* ── 기억을 언제 버리나 ────────────────────────────────────────────────────
|
|
103
|
+
* 기억한 종류가 지금 목록에 없으면 따르지 않는다. 대화가 하나 없어진 판으로 바뀌었는데 그 이름으로
|
|
104
|
+
* 열려고 하면 대화가 안 열리고, 사람은 자기가 무엇을 잘못했는지 알 길이 없다. 그때는 host 가 준
|
|
105
|
+
* 대화로 둔다.
|
|
93
106
|
*
|
|
94
|
-
*
|
|
95
|
-
* separate question (standard A2).
|
|
107
|
+
* 고를 것이 하나뿐이면 기억을 보지 않는다 — 고른 적이 없는 화면이다.
|
|
96
108
|
*/
|
|
97
|
-
export declare function
|
|
109
|
+
export declare function pickInitialMode(current: string | undefined, choices: ReadonlyArray<AssistantModeChoice>, remembered: string | undefined): string | undefined;
|
|
@@ -1,36 +1,80 @@
|
|
|
1
1
|
/** True when these two describe different conversations, so the old one should be closed. */
|
|
2
2
|
export function isDifferentConversation(a, b) {
|
|
3
|
-
return
|
|
3
|
+
return `${a?.kind ?? ''}|${a?.target ?? ''}` !== `${b?.kind ?? ''}|${b?.target ?? ''}`;
|
|
4
4
|
}
|
|
5
5
|
/**
|
|
6
6
|
* Reconcile the places a setting can come from.
|
|
7
7
|
*
|
|
8
|
-
* **A host that declares a mode gets the mode and nothing else.**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* wants those declares them.
|
|
8
|
+
* **A host that declares a mode gets the mode and nothing else.** What the mode does not offer is
|
|
9
|
+
* not filled in from the element's own properties or from the defaults another package registered.
|
|
10
|
+
* Filling it back in is how the defect this type removes returned — board-ai registers board
|
|
11
|
+
* authoring's `/add-monitor`, its component `@` catalog and its "previewed on the board" line
|
|
12
|
+
* globally, and plant's factory mode, which declares no slash list, offered them on a factory
|
|
13
|
+
* screen once the board modeller had been loaded.
|
|
15
14
|
*
|
|
16
|
-
* A
|
|
17
|
-
*
|
|
15
|
+
* A mode's offering arrives asynchronously, so until the server has answered a mode offers
|
|
16
|
+
* nothing. Standing the defaults in during that gap would show the board's list for a moment on a
|
|
17
|
+
* screen that does not edit boards.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* A host that declares no mode gets exactly what it got before modes existed — its own properties,
|
|
20
|
+
* falling back to the registered defaults — and it must name its own door: the `boardAIChat`
|
|
21
|
+
* default was removed with decision 7, because a missing door should show when a surface is
|
|
22
|
+
* declared, not when a person says something.
|
|
21
23
|
*/
|
|
22
|
-
export function resolveConversation(mode, own, defaults) {
|
|
23
|
-
|
|
24
|
+
export function resolveConversation(mode, own, defaults, view) {
|
|
25
|
+
if (mode) {
|
|
26
|
+
/* A view for another kind is the previous conversation's answer arriving late. */
|
|
27
|
+
const offered = view && view.kind === mode.kind ? view : undefined;
|
|
28
|
+
return {
|
|
29
|
+
kind: mode.kind,
|
|
30
|
+
target: mode.target,
|
|
31
|
+
hostContext: mode.hostContext,
|
|
32
|
+
chatEndpoint: 'assistantChat',
|
|
33
|
+
catalogEntries: offered?.catalogEntries ?? [],
|
|
34
|
+
slashTemplates: offered?.slashTemplates ?? [],
|
|
35
|
+
proposalChoices: offered?.proposalChoices ?? [],
|
|
36
|
+
placeholder: offered?.placeholder,
|
|
37
|
+
footerNotice: offered?.footerNotice,
|
|
38
|
+
stagedNotice: offered?.stagedNotice
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const pick = (key) => own[key] ?? defaults[key];
|
|
42
|
+
const chatEndpoint = pick('chatEndpoint');
|
|
43
|
+
if (!chatEndpoint) {
|
|
44
|
+
throw new Error('[ox-assistant-chat] a surface with no mode must name its chatEndpoint. The `boardAIChat` default was ' +
|
|
45
|
+
'removed (ADR-0061 decision 7) — apps declare a mode and talk to `assistantChat`; the board product ' +
|
|
46
|
+
'names `boardAIChat` on the element.');
|
|
47
|
+
}
|
|
24
48
|
return {
|
|
25
|
-
systemPrompt: pick('systemPrompt'),
|
|
26
|
-
toolCategories: pick('toolCategories'),
|
|
27
49
|
hostContext: pick('hostContext'),
|
|
28
|
-
|
|
50
|
+
placeholder: pick('placeholder'),
|
|
51
|
+
chatEndpoint,
|
|
29
52
|
catalogEntries: pick('catalogEntries') ?? [],
|
|
30
53
|
slashTemplates: pick('slashTemplates') ?? [],
|
|
31
|
-
|
|
54
|
+
footerNotice: pick('footerNotice'),
|
|
32
55
|
proposalChoices: pick('proposalChoices') ?? [],
|
|
33
|
-
|
|
56
|
+
stagedNotice: pick('stagedNotice')
|
|
34
57
|
};
|
|
35
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* 이 화면을 열 때 어느 대화로 설 것인가 — ADR-0072 A17 ①.
|
|
61
|
+
*
|
|
62
|
+
* ── 왜 기억하나 ───────────────────────────────────────────────────────────
|
|
63
|
+
* 보드를 열 때마다 저작 대화로 되돌아가면, 현장을 물으려고 온 사람은 화면을 열 때마다 같은 손질을
|
|
64
|
+
* 다시 한다. 기억은 이 브라우저에만 남는다 — 서버에 칸을 만들지 않는다.
|
|
65
|
+
*
|
|
66
|
+
* ── 기억을 언제 버리나 ────────────────────────────────────────────────────
|
|
67
|
+
* 기억한 종류가 지금 목록에 없으면 따르지 않는다. 대화가 하나 없어진 판으로 바뀌었는데 그 이름으로
|
|
68
|
+
* 열려고 하면 대화가 안 열리고, 사람은 자기가 무엇을 잘못했는지 알 길이 없다. 그때는 host 가 준
|
|
69
|
+
* 대화로 둔다.
|
|
70
|
+
*
|
|
71
|
+
* 고를 것이 하나뿐이면 기억을 보지 않는다 — 고른 적이 없는 화면이다.
|
|
72
|
+
*/
|
|
73
|
+
export function pickInitialMode(current, choices, remembered) {
|
|
74
|
+
if (choices.length < 2)
|
|
75
|
+
return current;
|
|
76
|
+
if (!remembered || remembered === current)
|
|
77
|
+
return current;
|
|
78
|
+
return choices.some(choice => choice.kind === remembered) ? remembered : current;
|
|
79
|
+
}
|
|
36
80
|
//# sourceMappingURL=assistant-mode.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant-mode.js","sourceRoot":"","sources":["../../client/components/assistant-mode.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"assistant-mode.js","sourceRoot":"","sources":["../../client/components/assistant-mode.ts"],"names":[],"mappings":"AAqDA,6FAA6F;AAC7F,MAAM,UAAU,uBAAuB,CAAC,CAAiB,EAAE,CAAiB;IAC1E,OAAO,GAAG,CAAC,EAAE,IAAI,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,IAAI,EAAE,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,IAAI,EAAE,EAAE,CAAA;AACxF,CAAC;AAoBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAA+B,EAC/B,GAAkC,EAClC,QAAuC,EACvC,IAAwB;IAExB,IAAI,IAAI,EAAE,CAAC;QACT,kFAAkF;QAClF,MAAM,OAAO,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;QAClE,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,YAAY,EAAE,eAAe;YAC7B,cAAc,EAAE,OAAO,EAAE,cAAc,IAAI,EAAE;YAC7C,cAAc,EAAE,OAAO,EAAE,cAAc,IAAI,EAAE;YAC7C,eAAe,EAAE,OAAO,EAAE,eAAe,IAAI,EAAE;YAC/C,WAAW,EAAE,OAAO,EAAE,WAAW;YACjC,YAAY,EAAE,OAAO,EAAE,YAAY;YACnC,YAAY,EAAE,OAAO,EAAE,YAAY;SACpC,CAAA;IACH,CAAC;IAED,MAAM,IAAI,GAAG,CAAuC,GAAM,EAA2B,EAAE,CACpF,GAAW,CAAC,GAAG,CAAC,IAAK,QAAgB,CAAC,GAAG,CAAC,CAAA;IAE7C,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC,CAAA;IACzC,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,uGAAuG;YACrG,qGAAqG;YACrG,qCAAqC,CACxC,CAAA;IACH,CAAC;IAED,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC;QAChC,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC;QAChC,YAAY;QACZ,cAAc,EAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE;QAC5C,cAAc,EAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE;QAC5C,YAAY,EAAE,IAAI,CAAC,cAAc,CAAC;QAClC,eAAe,EAAE,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE;QAC9C,YAAY,EAAE,IAAI,CAAC,cAAc,CAAC;KACnC,CAAA;AACH,CAAC;AASD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAC7B,OAA2B,EAC3B,OAA2C,EAC3C,UAA8B;IAE9B,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,OAAO,CAAA;IACtC,IAAI,CAAC,UAAU,IAAI,UAAU,KAAK,OAAO;QAAE,OAAO,OAAO,CAAA;IACzD,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAA;AAClF,CAAC","sourcesContent":["/**\n * A mode is what a conversation *is*: which conversation this is, and what it is about.\n *\n * Those two used to be nine. The browser declared the persona, the tool categories, the model and\n * the limits and sent them with every turn, which meant a caller holding the door's privilege\n * chose its own authority — and it meant a screen could describe a conversation one way while the\n * server answered it another. Since ADR-0061 decision 7 the app server registers the mode and the\n * browser names it:\n *\n * ```ts\n * html`<ox-assistant-chat .mode=${{ kind: 'twin.space', target: spaceId, hostContext: { spaceId } }}></ox-assistant-chat>`\n * ```\n *\n * What the panel shows — the `/` sentences, the `@` catalog, the two notices — comes from the same\n * declaration, fetched from the server already written in the person's language, so the panel and\n * the door cannot offer different conversations.\n */\nexport interface AssistantMode {\n /**\n * Which conversation this is — a `<app>.<surface>` kind the app server registered.\n */\n kind: string\n\n /**\n * What it is about: the space, the station, the model being edited.\n *\n * **With `kind`, this is the conversation's lifetime.** When either changes the chat closes what\n * is on screen and starts again — a conversation about station 3 is not a conversation about\n * station 7, and leaving the old one up means the person reads an answer about a screen they are\n * no longer looking at.\n */\n target?: string\n\n /**\n * What the host knows about the subject; travels with each request.\n *\n * **The model never sees this** — it reaches the tools as `ctx.host` and nothing else (ADR-0061\n * decision 7). Anything the model must know is answered by a read tool.\n */\n hostContext?: any\n}\n\n/** What the server said this mode offers, already in the request's locale. */\nexport interface AssistantModeView {\n kind: string\n slashTemplates: any[]\n catalogEntries: any[]\n proposalChoices: any[]\n placeholder?: string\n footerNotice?: string\n stagedNotice?: string\n}\n\n/** True when these two describe different conversations, so the old one should be closed. */\nexport function isDifferentConversation(a?: AssistantMode, b?: AssistantMode): boolean {\n return `${a?.kind ?? ''}|${a?.target ?? ''}` !== `${b?.kind ?? ''}|${b?.target ?? ''}`\n}\n\n/** Everything a conversation needs, after the mode, the host's own properties, the registered\n * defaults and the server's answer have been reconciled. */\nexport interface ConversationSettings {\n /** Set when a mode names this conversation; absent for a host that declares none. */\n kind?: string\n target?: string\n hostContext?: any\n chatEndpoint: 'boardAIChat' | 'assistantChat'\n catalogEntries: any[]\n slashTemplates: any[]\n /** What the empty input says. A mode's comes translated from the server. */\n placeholder?: string\n /** Sentences, not keys — a mode's come translated from the server, a host's from its own i18n. */\n footerNotice?: string\n proposalChoices: any[]\n stagedNotice?: string\n}\n\n/**\n * Reconcile the places a setting can come from.\n *\n * **A host that declares a mode gets the mode and nothing else.** What the mode does not offer is\n * not filled in from the element's own properties or from the defaults another package registered.\n * Filling it back in is how the defect this type removes returned — board-ai registers board\n * authoring's `/add-monitor`, its component `@` catalog and its \"previewed on the board\" line\n * globally, and plant's factory mode, which declares no slash list, offered them on a factory\n * screen once the board modeller had been loaded.\n *\n * A mode's offering arrives asynchronously, so until the server has answered a mode offers\n * nothing. Standing the defaults in during that gap would show the board's list for a moment on a\n * screen that does not edit boards.\n *\n * A host that declares no mode gets exactly what it got before modes existed — its own properties,\n * falling back to the registered defaults — and it must name its own door: the `boardAIChat`\n * default was removed with decision 7, because a missing door should show when a surface is\n * declared, not when a person says something.\n */\nexport function resolveConversation(\n mode: AssistantMode | undefined,\n own: Partial<ConversationSettings>,\n defaults: Partial<ConversationSettings>,\n view?: AssistantModeView\n): ConversationSettings {\n if (mode) {\n /* A view for another kind is the previous conversation's answer arriving late. */\n const offered = view && view.kind === mode.kind ? view : undefined\n return {\n kind: mode.kind,\n target: mode.target,\n hostContext: mode.hostContext,\n chatEndpoint: 'assistantChat',\n catalogEntries: offered?.catalogEntries ?? [],\n slashTemplates: offered?.slashTemplates ?? [],\n proposalChoices: offered?.proposalChoices ?? [],\n placeholder: offered?.placeholder,\n footerNotice: offered?.footerNotice,\n stagedNotice: offered?.stagedNotice\n }\n }\n\n const pick = <K extends keyof ConversationSettings>(key: K): ConversationSettings[K] =>\n (own as any)[key] ?? (defaults as any)[key]\n\n const chatEndpoint = pick('chatEndpoint')\n if (!chatEndpoint) {\n throw new Error(\n '[ox-assistant-chat] a surface with no mode must name its chatEndpoint. The `boardAIChat` default was ' +\n 'removed (ADR-0061 decision 7) — apps declare a mode and talk to `assistantChat`; the board product ' +\n 'names `boardAIChat` on the element.'\n )\n }\n\n return {\n hostContext: pick('hostContext'),\n placeholder: pick('placeholder'),\n chatEndpoint,\n catalogEntries: pick('catalogEntries') ?? [],\n slashTemplates: pick('slashTemplates') ?? [],\n footerNotice: pick('footerNotice'),\n proposalChoices: pick('proposalChoices') ?? [],\n stagedNotice: pick('stagedNotice')\n }\n}\n\n/** 한 subject 위에 설 수 있는 대화 하나 — 서버가 준 목록의 한 줄. */\nexport interface AssistantModeChoice {\n kind: string\n label: string\n isDefault: boolean\n}\n\n/**\n * 이 화면을 열 때 어느 대화로 설 것인가 — ADR-0072 A17 ①.\n *\n * ── 왜 기억하나 ───────────────────────────────────────────────────────────\n * 보드를 열 때마다 저작 대화로 되돌아가면, 현장을 물으려고 온 사람은 화면을 열 때마다 같은 손질을\n * 다시 한다. 기억은 이 브라우저에만 남는다 — 서버에 칸을 만들지 않는다.\n *\n * ── 기억을 언제 버리나 ────────────────────────────────────────────────────\n * 기억한 종류가 지금 목록에 없으면 따르지 않는다. 대화가 하나 없어진 판으로 바뀌었는데 그 이름으로\n * 열려고 하면 대화가 안 열리고, 사람은 자기가 무엇을 잘못했는지 알 길이 없다. 그때는 host 가 준\n * 대화로 둔다.\n *\n * 고를 것이 하나뿐이면 기억을 보지 않는다 — 고른 적이 없는 화면이다.\n */\nexport function pickInitialMode(\n current: string | undefined,\n choices: ReadonlyArray<AssistantModeChoice>,\n remembered: string | undefined\n): string | undefined {\n if (choices.length < 2) return current\n if (!remembered || remembered === current) return current\n return choices.some(choice => choice.kind === remembered) ? remembered : current\n}\n"]}
|
|
@@ -1,85 +1,106 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* A mode is one value, and the point of it is that the parts of a conversation cannot
|
|
3
|
-
*
|
|
2
|
+
* A mode is one value, and the point of it is that the parts of a conversation cannot disagree
|
|
3
|
+
* with each other. Since ADR-0061 decision 7 the parts live on the server: the browser names the
|
|
4
|
+
* conversation (`kind`) and its subject (`target`), and the persona, the tool categories, the
|
|
5
|
+
* model and the limits are the app server's.
|
|
4
6
|
*
|
|
5
|
-
* The defect this guards against shipped: a screen whose
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* The defect this still guards against shipped: a screen whose prompt said "you do not edit boards
|
|
8
|
+
* here" offered `/add-monitor` in the same panel, because the prompt was an element property and
|
|
9
|
+
* the slash list was a module global. A mode's wording now comes from the same declaration the
|
|
10
|
+
* door reads, so the two cannot describe different conversations.
|
|
9
11
|
*/
|
|
10
|
-
import { isDifferentConversation, resolveConversation } from './assistant-mode';
|
|
11
|
-
const MODE = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
import { isDifferentConversation, resolveConversation, pickInitialMode } from './assistant-mode';
|
|
13
|
+
const MODE = { kind: 'twin.space', target: 'S-12', hostContext: { spaceId: 'S-12' } };
|
|
14
|
+
/** What the server said this mode offers, already in the request's locale. */
|
|
15
|
+
const MODE_VIEW = {
|
|
16
|
+
kind: 'twin.space',
|
|
17
|
+
slashTemplates: [{ name: 'status', description: 'summarize', template: '이 공간의 지금 상태를 요약해줘' }],
|
|
18
|
+
catalogEntries: [],
|
|
19
|
+
proposalChoices: [],
|
|
20
|
+
footerNotice: undefined,
|
|
21
|
+
stagedNotice: undefined
|
|
17
22
|
};
|
|
23
|
+
/** The board product, which declares no mode and names its own door. */
|
|
18
24
|
const HOST_PROPERTIES = {
|
|
19
|
-
systemPrompt: 'a prompt the host set the old way',
|
|
20
|
-
toolCategories: ['board-ai'],
|
|
21
25
|
hostContext: { boardId: 'B-1' },
|
|
22
26
|
chatEndpoint: 'boardAIChat'
|
|
23
27
|
};
|
|
24
28
|
const GLOBAL_DEFAULTS = {
|
|
25
29
|
catalogEntries: [{ type: 'rect' }],
|
|
26
30
|
slashTemplates: [{ name: 'add-monitor', template: '모니터를 추가해줘' }],
|
|
27
|
-
|
|
31
|
+
footerNotice: 'previewed on the board'
|
|
28
32
|
};
|
|
29
|
-
describe('
|
|
30
|
-
test('
|
|
31
|
-
const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS);
|
|
32
|
-
expect(c.
|
|
33
|
-
expect(c.
|
|
33
|
+
describe('a mode names the conversation; the server says what it offers', () => {
|
|
34
|
+
test('★ the subject travels, and the door is the neutral one', () => {
|
|
35
|
+
const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS, MODE_VIEW);
|
|
36
|
+
expect(c.kind).toBe('twin.space');
|
|
37
|
+
expect(c.target).toBe('S-12');
|
|
34
38
|
expect(c.hostContext).toEqual({ spaceId: 'S-12' });
|
|
39
|
+
expect(c.chatEndpoint).toBe('assistantChat');
|
|
35
40
|
});
|
|
36
|
-
test('
|
|
37
|
-
const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS);
|
|
38
|
-
expect(c.slashTemplates).toEqual(
|
|
39
|
-
/* A mode that says it does not edit boards must not offer a board edit. */
|
|
41
|
+
test('★ what the mode offers comes from the server, not from what another package registered', () => {
|
|
42
|
+
const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS, MODE_VIEW);
|
|
43
|
+
expect(c.slashTemplates).toEqual(MODE_VIEW.slashTemplates);
|
|
40
44
|
expect(JSON.stringify(c.slashTemplates)).not.toContain('add-monitor');
|
|
41
|
-
});
|
|
42
|
-
test('★ what a mode leaves out stays out — the defaults another package registered do not fill it', () => {
|
|
43
|
-
const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS);
|
|
44
|
-
/* The board's `@` catalog and its "previewed on the board" line belong to board authoring. */
|
|
45
45
|
expect(c.catalogEntries).toEqual([]);
|
|
46
|
-
expect(c.
|
|
46
|
+
expect(c.footerNotice).toBeUndefined();
|
|
47
47
|
});
|
|
48
|
-
test('★
|
|
49
|
-
|
|
50
|
-
const { slashTemplates, ...withoutSlash } = MODE;
|
|
51
|
-
const c = resolveConversation({ ...withoutSlash, id: 'plant:factory:D-1' }, HOST_PROPERTIES, GLOBAL_DEFAULTS);
|
|
48
|
+
test('★ before the server has answered, a mode offers nothing — the defaults do not stand in', () => {
|
|
49
|
+
const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS, undefined);
|
|
52
50
|
expect(c.slashTemplates).toEqual([]);
|
|
51
|
+
expect(c.catalogEntries).toEqual([]);
|
|
53
52
|
});
|
|
54
|
-
test('
|
|
55
|
-
const c = resolveConversation({
|
|
56
|
-
expect(c.
|
|
57
|
-
expect(c.toolCategories).toBeUndefined();
|
|
58
|
-
expect(c.hostContext).toBeUndefined();
|
|
53
|
+
test('a view for another kind is ignored — the panel is showing this conversation', () => {
|
|
54
|
+
const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS, { ...MODE_VIEW, kind: 'twin.board' });
|
|
55
|
+
expect(c.slashTemplates).toEqual([]);
|
|
59
56
|
});
|
|
60
|
-
|
|
57
|
+
});
|
|
58
|
+
describe('a host with no mode', () => {
|
|
59
|
+
test('gets its own properties and the registered defaults, as before modes existed', () => {
|
|
61
60
|
const c = resolveConversation(undefined, HOST_PROPERTIES, GLOBAL_DEFAULTS);
|
|
62
|
-
expect(c.
|
|
63
|
-
expect(c.toolCategories).toEqual(HOST_PROPERTIES.toolCategories);
|
|
61
|
+
expect(c.hostContext).toEqual({ boardId: 'B-1' });
|
|
64
62
|
expect(c.slashTemplates).toEqual(GLOBAL_DEFAULTS.slashTemplates);
|
|
65
|
-
|
|
66
|
-
test('nothing anywhere resolves to empty lists and the board endpoint, never undefined', () => {
|
|
67
|
-
const c = resolveConversation(undefined, {}, {});
|
|
68
|
-
expect(c.catalogEntries).toEqual([]);
|
|
69
|
-
expect(c.slashTemplates).toEqual([]);
|
|
70
|
-
expect(c.proposalChoices).toEqual([]);
|
|
63
|
+
expect(c.footerNotice).toBe('previewed on the board');
|
|
71
64
|
expect(c.chatEndpoint).toBe('boardAIChat');
|
|
72
65
|
});
|
|
66
|
+
test('★ must name its door — there is no default any more', () => {
|
|
67
|
+
expect(() => resolveConversation(undefined, {}, {})).toThrow(/chatEndpoint/);
|
|
68
|
+
});
|
|
73
69
|
});
|
|
74
70
|
describe('isDifferentConversation', () => {
|
|
75
|
-
const station3 = {
|
|
76
|
-
test('a different subject ends the conversation', () => {
|
|
77
|
-
expect(isDifferentConversation(station3, {
|
|
71
|
+
const station3 = { kind: 'plant.station', target: '3' };
|
|
72
|
+
test('★ a different subject ends the conversation, and so does a different kind', () => {
|
|
73
|
+
expect(isDifferentConversation(station3, { kind: 'plant.station', target: '7' })).toBe(true);
|
|
74
|
+
expect(isDifferentConversation(station3, { kind: 'plant.factory', target: '3' })).toBe(true);
|
|
78
75
|
expect(isDifferentConversation(undefined, station3)).toBe(true);
|
|
79
76
|
});
|
|
80
|
-
test('
|
|
81
|
-
expect(isDifferentConversation(station3, { ...station3,
|
|
77
|
+
test('the same subject, described again, does not', () => {
|
|
78
|
+
expect(isDifferentConversation(station3, { ...station3, hostContext: { reread: true } })).toBe(false);
|
|
82
79
|
expect(isDifferentConversation(undefined, undefined)).toBe(false);
|
|
83
80
|
});
|
|
84
81
|
});
|
|
82
|
+
/*
|
|
83
|
+
* ADR-0072 A17 ① — 화면을 열 때 어느 대화로 서나.
|
|
84
|
+
*
|
|
85
|
+
* 되살리는 것은 고른 대화이지 세션이 아니다. 그리고 없어진 대화로 되살리지 않는다 — 그러면 대화가
|
|
86
|
+
* 안 열리고 사람은 자기가 무엇을 잘못했는지 알 길이 없다.
|
|
87
|
+
*/
|
|
88
|
+
describe('pickInitialMode', () => {
|
|
89
|
+
const choices = [
|
|
90
|
+
{ kind: 'twin.board', label: '이 그림 고치기', isDefault: true },
|
|
91
|
+
{ kind: 'twin.space', label: '이 현장 지금', isDefault: false }
|
|
92
|
+
];
|
|
93
|
+
it('★ 지난번에 고른 대화로 돌아간다 — 매번 같은 손질을 다시 하지 않는다', () => {
|
|
94
|
+
expect(pickInitialMode('twin.board', choices, 'twin.space')).toBe('twin.space');
|
|
95
|
+
});
|
|
96
|
+
it('★ 지금 목록에 없는 기억은 따르지 않는다 — 안 열리는 대화로 들어간다', () => {
|
|
97
|
+
expect(pickInitialMode('twin.board', choices, 'twin.gone')).toBe('twin.board');
|
|
98
|
+
});
|
|
99
|
+
it('★ 고를 것이 하나뿐이면 기억을 보지 않는다 — 고른 적이 없는 화면이다', () => {
|
|
100
|
+
expect(pickInitialMode('plant.station', [choices[0]], 'twin.space')).toBe('plant.station');
|
|
101
|
+
});
|
|
102
|
+
it('기억이 없으면 host 가 준 것으로 선다', () => {
|
|
103
|
+
expect(pickInitialMode('twin.board', choices, undefined)).toBe('twin.board');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
85
106
|
//# sourceMappingURL=assistant-mode.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant-mode.test.js","sourceRoot":"","sources":["../../client/components/assistant-mode.test.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"assistant-mode.test.js","sourceRoot":"","sources":["../../client/components/assistant-mode.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * A mode is one value, and the point of it is that the parts of a conversation cannot disagree\n * with each other. Since ADR-0061 decision 7 the parts live on the server: the browser names the\n * conversation (`kind`) and its subject (`target`), and the persona, the tool categories, the\n * model and the limits are the app server's.\n *\n * The defect this still guards against shipped: a screen whose prompt said \"you do not edit boards\n * here\" offered `/add-monitor` in the same panel, because the prompt was an element property and\n * the slash list was a module global. A mode's wording now comes from the same declaration the\n * door reads, so the two cannot describe different conversations.\n */\nimport { isDifferentConversation, resolveConversation, type AssistantMode, pickInitialMode } from './assistant-mode'\n\nconst MODE: AssistantMode = { kind: 'twin.space', target: 'S-12', hostContext: { spaceId: 'S-12' } }\n\n/** What the server said this mode offers, already in the request's locale. */\nconst MODE_VIEW = {\n kind: 'twin.space',\n slashTemplates: [{ name: 'status', description: 'summarize', template: '이 공간의 지금 상태를 요약해줘' }],\n catalogEntries: [],\n proposalChoices: [],\n footerNotice: undefined,\n stagedNotice: undefined\n}\n\n/** The board product, which declares no mode and names its own door. */\nconst HOST_PROPERTIES = {\n hostContext: { boardId: 'B-1' },\n chatEndpoint: 'boardAIChat' as const\n}\n\nconst GLOBAL_DEFAULTS = {\n catalogEntries: [{ type: 'rect' }],\n slashTemplates: [{ name: 'add-monitor', template: '모니터를 추가해줘' }],\n footerNotice: 'previewed on the board'\n}\n\ndescribe('a mode names the conversation; the server says what it offers', () => {\n test('★ the subject travels, and the door is the neutral one', () => {\n const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS, MODE_VIEW)\n\n expect(c.kind).toBe('twin.space')\n expect(c.target).toBe('S-12')\n expect(c.hostContext).toEqual({ spaceId: 'S-12' })\n expect(c.chatEndpoint).toBe('assistantChat')\n })\n\n test('★ what the mode offers comes from the server, not from what another package registered', () => {\n const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS, MODE_VIEW)\n\n expect(c.slashTemplates).toEqual(MODE_VIEW.slashTemplates)\n expect(JSON.stringify(c.slashTemplates)).not.toContain('add-monitor')\n expect(c.catalogEntries).toEqual([])\n expect(c.footerNotice).toBeUndefined()\n })\n\n test('★ before the server has answered, a mode offers nothing — the defaults do not stand in', () => {\n const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS, undefined)\n\n expect(c.slashTemplates).toEqual([])\n expect(c.catalogEntries).toEqual([])\n })\n\n test('a view for another kind is ignored — the panel is showing this conversation', () => {\n const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS, { ...MODE_VIEW, kind: 'twin.board' })\n\n expect(c.slashTemplates).toEqual([])\n })\n})\n\ndescribe('a host with no mode', () => {\n test('gets its own properties and the registered defaults, as before modes existed', () => {\n const c = resolveConversation(undefined, HOST_PROPERTIES, GLOBAL_DEFAULTS)\n\n expect(c.hostContext).toEqual({ boardId: 'B-1' })\n expect(c.slashTemplates).toEqual(GLOBAL_DEFAULTS.slashTemplates)\n expect(c.footerNotice).toBe('previewed on the board')\n expect(c.chatEndpoint).toBe('boardAIChat')\n })\n\n test('★ must name its door — there is no default any more', () => {\n expect(() => resolveConversation(undefined, {}, {})).toThrow(/chatEndpoint/)\n })\n})\n\ndescribe('isDifferentConversation', () => {\n const station3: AssistantMode = { kind: 'plant.station', target: '3' }\n\n test('★ a different subject ends the conversation, and so does a different kind', () => {\n expect(isDifferentConversation(station3, { kind: 'plant.station', target: '7' })).toBe(true)\n expect(isDifferentConversation(station3, { kind: 'plant.factory', target: '3' })).toBe(true)\n expect(isDifferentConversation(undefined, station3)).toBe(true)\n })\n\n test('the same subject, described again, does not', () => {\n expect(isDifferentConversation(station3, { ...station3, hostContext: { reread: true } })).toBe(false)\n expect(isDifferentConversation(undefined, undefined)).toBe(false)\n })\n})\n\n/*\n * ADR-0072 A17 ① — 화면을 열 때 어느 대화로 서나.\n *\n * 되살리는 것은 고른 대화이지 세션이 아니다. 그리고 없어진 대화로 되살리지 않는다 — 그러면 대화가\n * 안 열리고 사람은 자기가 무엇을 잘못했는지 알 길이 없다.\n */\ndescribe('pickInitialMode', () => {\n const choices = [\n { kind: 'twin.board', label: '이 그림 고치기', isDefault: true },\n { kind: 'twin.space', label: '이 현장 지금', isDefault: false }\n ]\n\n it('★ 지난번에 고른 대화로 돌아간다 — 매번 같은 손질을 다시 하지 않는다', () => {\n expect(pickInitialMode('twin.board', choices, 'twin.space')).toBe('twin.space')\n })\n\n it('★ 지금 목록에 없는 기억은 따르지 않는다 — 안 열리는 대화로 들어간다', () => {\n expect(pickInitialMode('twin.board', choices, 'twin.gone')).toBe('twin.board')\n })\n\n it('★ 고를 것이 하나뿐이면 기억을 보지 않는다 — 고른 적이 없는 화면이다', () => {\n expect(pickInitialMode('plant.station', [choices[0]], 'twin.space')).toBe('plant.station')\n })\n\n it('기억이 없으면 host 가 준 것으로 선다', () => {\n expect(pickInitialMode('twin.board', choices, undefined)).toBe('twin.board')\n })\n})\n"]}
|
|
@@ -22,6 +22,8 @@ export interface RemoteMessage {
|
|
|
22
22
|
name?: string;
|
|
23
23
|
email?: string;
|
|
24
24
|
} | null;
|
|
25
|
+
/** 이 사람이 이 답의 문을 못 지나 본문이 한 줄로 바뀌어 왔나(ADR-0073 판정 1). */
|
|
26
|
+
redacted?: boolean;
|
|
25
27
|
}
|
|
26
28
|
/** 방송 메시지 → 라인. */
|
|
27
29
|
export declare function remoteMessageToLine(m: RemoteMessage): DedupLine;
|