@things-factory/ai-assistant 10.1.27 → 10.1.29
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 +701 -63
- package/client/components/assistant-mode.test.ts +105 -55
- package/client/components/assistant-mode.ts +144 -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 +54 -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 +37 -1
- package/client/utils/assistant-session-transport.ts +13 -5
- package/dist-client/components/assistant-chat.d.ts +140 -13
- package/dist-client/components/assistant-chat.js +659 -71
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/components/assistant-mode.d.ts +102 -68
- package/dist-client/components/assistant-mode.js +79 -19
- package/dist-client/components/assistant-mode.js.map +1 -1
- package/dist-client/components/assistant-mode.test.js +90 -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 +49 -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 +20 -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 +13 -5
- package/dist-client/utils/assistant-session-transport.js.map +1 -1
- package/dist-server/door-declaration-report.d.ts +14 -0
- package/dist-server/door-declaration-report.js +54 -0
- package/dist-server/door-declaration-report.js.map +1 -0
- 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 +185 -44
- package/dist-server/service/assistant-chat-resolver.js.map +1 -1
- package/dist-server/service/assistant-mode/assistant-mode-resolver.d.ts +33 -0
- package/dist-server/service/assistant-mode/assistant-mode-resolver.js +148 -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 +43 -0
- package/dist-server/service/assistant-mode/resolve.js +65 -0
- package/dist-server/service/assistant-mode/resolve.js.map +1 -0
- package/dist-server/service/assistant-mode/types.d.ts +120 -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 +92 -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 +27 -0
- package/dist-server/service/chat-message/door-access.js +34 -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 +39 -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 +36 -0
- package/dist-server/service/chat-session/chat-session.js +27 -2
- package/dist-server/service/chat-session/chat-session.js.map +1 -1
- package/dist-server/service/chat-session/session-anchor.d.ts +10 -1
- package/dist-server/service/chat-session/session-anchor.js +12 -4
- package/dist-server/service/chat-session/session-anchor.js.map +1 -1
- package/dist-server/service/chat-session/session-inbox.d.ts +6 -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-kind.d.ts +14 -0
- package/dist-server/service/chat-session/session-kind.js +30 -0
- package/dist-server/service/chat-session/session-kind.js.map +1 -0
- package/dist-server/service/chat-session/session-visibility.d.ts +15 -0
- package/dist-server/service/chat-session/session-visibility.js +35 -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 +17 -0
- package/dist-server/service/chat-session-resolver.js +271 -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 +9 -1
- package/dist-server/service/index.js +21 -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 +50 -0
- package/dist-server/service/testing/mode-conformance.js +103 -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 +32 -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/door-declaration-report.test.ts +53 -0
- package/server/door-declaration-report.ts +61 -0
- 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 +205 -42
- package/server/service/assistant-mode/assistant-mode-resolver.ts +108 -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 +120 -0
- package/server/service/assistant-mode/resolve.ts +105 -0
- package/server/service/assistant-mode/subject-defaults.test.ts +86 -0
- package/server/service/assistant-mode/types.ts +121 -0
- package/server/service/chat-message/carried-turns.test.ts +80 -0
- package/server/service/chat-message/carried-turns.ts +112 -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.test.ts +72 -0
- package/server/service/chat-message/door-access.ts +65 -0
- package/server/service/chat-message/fold-history-window.test.ts +184 -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 +44 -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 +46 -1
- package/server/service/chat-session/session-anchor.ts +12 -3
- package/server/service/chat-session/session-inbox.test.ts +18 -0
- package/server/service/chat-session/session-inbox.ts +11 -2
- package/server/service/chat-session/session-kind.test.ts +24 -0
- package/server/service/chat-session/session-kind.ts +32 -0
- package/server/service/chat-session/session-visibility.test.ts +52 -0
- package/server/service/chat-session/session-visibility.ts +34 -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 +276 -15
- package/server/service/grounding-sources.test.ts +55 -0
- package/server/service/grounding-sources.ts +46 -0
- package/server/service/index.ts +21 -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 +122 -0
- package/server/tool-registry-boot-report.ts +31 -2
- package/test/assistant-chat-call-sites.test.ts +82 -0
- package/translations/en.json +46 -3
- package/translations/ja.json +46 -3
- package/translations/ko.json +46 -3
- package/translations/ms.json +46 -3
- package/translations/zh.json +46 -3
|
@@ -1,97 +1,131 @@
|
|
|
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
|
+
/** 빈 대화의 제목과 한 줄 — 이 대화가 무엇을 하는 자리인지. host 속성보다 앞선다. */
|
|
48
|
+
intro?: {
|
|
49
|
+
title: string;
|
|
50
|
+
subtitle: string;
|
|
51
|
+
};
|
|
52
|
+
/** 빈 대화의 예시 — 이 대화에서 실제로 할 수 있는 말. host 속성보다 앞선다. */
|
|
53
|
+
examples?: Array<{
|
|
54
|
+
label: string;
|
|
55
|
+
items: string[];
|
|
56
|
+
}>;
|
|
57
|
+
footerNotice?: string;
|
|
58
|
+
stagedNotice?: string;
|
|
64
59
|
}
|
|
65
60
|
/** True when these two describe different conversations, so the old one should be closed. */
|
|
66
61
|
export declare function isDifferentConversation(a?: AssistantMode, b?: AssistantMode): boolean;
|
|
67
|
-
/** Everything a conversation needs, after the mode, the host's own properties
|
|
68
|
-
*
|
|
62
|
+
/** Everything a conversation needs, after the mode, the host's own properties, the registered
|
|
63
|
+
* defaults and the server's answer have been reconciled. */
|
|
69
64
|
export interface ConversationSettings {
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
/** Set when a mode names this conversation; absent for a host that declares none. */
|
|
66
|
+
kind?: string;
|
|
67
|
+
target?: string;
|
|
72
68
|
hostContext?: any;
|
|
73
69
|
chatEndpoint: 'boardAIChat' | 'assistantChat';
|
|
74
70
|
catalogEntries: any[];
|
|
75
71
|
slashTemplates: any[];
|
|
76
|
-
|
|
72
|
+
/** What the empty input says. A mode's comes translated from the server. */
|
|
73
|
+
placeholder?: string;
|
|
74
|
+
/** Sentences, not keys — a mode's come translated from the server, a host's from its own i18n. */
|
|
75
|
+
footerNotice?: string;
|
|
77
76
|
proposalChoices: any[];
|
|
78
|
-
|
|
77
|
+
stagedNotice?: string;
|
|
79
78
|
}
|
|
80
79
|
/**
|
|
81
80
|
* Reconcile the places a setting can come from.
|
|
82
81
|
*
|
|
83
|
-
* **A host that declares a mode gets the mode and nothing else.**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
82
|
+
* **A host that declares a mode gets the mode and nothing else.** What the mode does not offer is
|
|
83
|
+
* not filled in from the element's own properties or from the defaults another package registered.
|
|
84
|
+
* Filling it back in is how the defect this type removes returned — board-ai registers board
|
|
85
|
+
* authoring's `/add-monitor`, its component `@` catalog and its "previewed on the board" line
|
|
86
|
+
* globally, and plant's factory mode, which declares no slash list, offered them on a factory
|
|
87
|
+
* screen once the board modeller had been loaded.
|
|
88
|
+
*
|
|
89
|
+
* A mode's offering arrives asynchronously, so until the server has answered a mode offers
|
|
90
|
+
* nothing. Standing the defaults in during that gap would show the board's list for a moment on a
|
|
91
|
+
* screen that does not edit boards.
|
|
92
|
+
*
|
|
93
|
+
* A host that declares no mode gets exactly what it got before modes existed — its own properties,
|
|
94
|
+
* falling back to the registered defaults — and it must name its own door: the `boardAIChat`
|
|
95
|
+
* default was removed with decision 7, because a missing door should show when a surface is
|
|
96
|
+
* declared, not when a person says something.
|
|
97
|
+
*/
|
|
98
|
+
export declare function resolveConversation(mode: AssistantMode | undefined, own: Partial<ConversationSettings>, defaults: Partial<ConversationSettings>, view?: AssistantModeView): ConversationSettings;
|
|
99
|
+
/** 한 subject 위에 설 수 있는 대화 하나 — 서버가 준 목록의 한 줄. */
|
|
100
|
+
export interface AssistantModeChoice {
|
|
101
|
+
kind: string;
|
|
102
|
+
label: string;
|
|
103
|
+
isDefault: boolean;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 이 화면을 열 때 어느 대화로 설 것인가 — ADR-0072 A17 ①.
|
|
107
|
+
*
|
|
108
|
+
* ── 왜 기억하나 ───────────────────────────────────────────────────────────
|
|
109
|
+
* 보드를 열 때마다 저작 대화로 되돌아가면, 현장을 물으려고 온 사람은 화면을 열 때마다 같은 손질을
|
|
110
|
+
* 다시 한다. 기억은 이 브라우저에만 남는다 — 서버에 칸을 만들지 않는다.
|
|
111
|
+
*
|
|
112
|
+
* ── 기억을 언제 버리나 ────────────────────────────────────────────────────
|
|
113
|
+
* 기억한 종류가 지금 목록에 없으면 따르지 않는다. 대화가 하나 없어진 판으로 바뀌었는데 그 이름으로
|
|
114
|
+
* 열려고 하면 대화가 안 열리고, 사람은 자기가 무엇을 잘못했는지 알 길이 없다. 그때는 host 가 준
|
|
115
|
+
* 대화로 둔다.
|
|
116
|
+
*
|
|
117
|
+
* 고를 것이 하나뿐이면 기억을 보지 않는다 — 고른 적이 없는 화면이다.
|
|
118
|
+
*/
|
|
119
|
+
export declare function pickInitialMode(current: string | undefined, choices: ReadonlyArray<AssistantModeChoice>, remembered: string | undefined): string | undefined;
|
|
120
|
+
/**
|
|
121
|
+
* 목록에서 고른 대화로 따라갈 것인가 — ADR-0072 결정 3.
|
|
122
|
+
*
|
|
123
|
+
* 목록은 한 대상의 대화를 **모두** 보여 주고, 그 대상 위에는 대화가 둘 설 수 있다(A17). 고른 것이 지금
|
|
124
|
+
* 열린 대화와 다른데 화면이 그대로 있으면 문이 거절하고(`session-kind-mismatch`), 목록은 눌리는데 안
|
|
125
|
+
* 열리는 목록이 된다 — 선언은 있고 길이 없는 그 모양이다.
|
|
90
126
|
*
|
|
91
|
-
*
|
|
92
|
-
* properties, falling back to the registered defaults.
|
|
127
|
+
* 칸이 서기 전에 난 세션에는 값이 없다. 그때는 지금 대화로 둔다 — 서버가 다음 턴에 지금 대화로 찍는다.
|
|
93
128
|
*
|
|
94
|
-
* `
|
|
95
|
-
* separate question (standard A2).
|
|
129
|
+
* `undefined` 는 「그대로 둔다」는 뜻이다.
|
|
96
130
|
*/
|
|
97
|
-
export declare function
|
|
131
|
+
export declare function followPickedConversation(pickedKind: string | undefined | null, currentKind: string | undefined): string | undefined;
|
|
@@ -1,36 +1,96 @@
|
|
|
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
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 목록에서 고른 대화로 따라갈 것인가 — ADR-0072 결정 3.
|
|
82
|
+
*
|
|
83
|
+
* 목록은 한 대상의 대화를 **모두** 보여 주고, 그 대상 위에는 대화가 둘 설 수 있다(A17). 고른 것이 지금
|
|
84
|
+
* 열린 대화와 다른데 화면이 그대로 있으면 문이 거절하고(`session-kind-mismatch`), 목록은 눌리는데 안
|
|
85
|
+
* 열리는 목록이 된다 — 선언은 있고 길이 없는 그 모양이다.
|
|
86
|
+
*
|
|
87
|
+
* 칸이 서기 전에 난 세션에는 값이 없다. 그때는 지금 대화로 둔다 — 서버가 다음 턴에 지금 대화로 찍는다.
|
|
88
|
+
*
|
|
89
|
+
* `undefined` 는 「그대로 둔다」는 뜻이다.
|
|
90
|
+
*/
|
|
91
|
+
export function followPickedConversation(pickedKind, currentKind) {
|
|
92
|
+
if (!pickedKind || pickedKind === currentKind)
|
|
93
|
+
return undefined;
|
|
94
|
+
return pickedKind;
|
|
95
|
+
}
|
|
36
96
|
//# 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":"AAyDA,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;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAAqC,EAAE,WAA+B;IAC7G,IAAI,CAAC,UAAU,IAAI,UAAU,KAAK,WAAW;QAAE,OAAO,SAAS,CAAA;IAC/D,OAAO,UAAU,CAAA;AACnB,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 /** 빈 대화의 제목과 한 줄 — 이 대화가 무엇을 하는 자리인지. host 속성보다 앞선다. */\n intro?: { title: string; subtitle: string }\n /** 빈 대화의 예시 — 이 대화에서 실제로 할 수 있는 말. host 속성보다 앞선다. */\n examples?: Array<{ label: string; items: 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\n/**\n * 목록에서 고른 대화로 따라갈 것인가 — ADR-0072 결정 3.\n *\n * 목록은 한 대상의 대화를 **모두** 보여 주고, 그 대상 위에는 대화가 둘 설 수 있다(A17). 고른 것이 지금\n * 열린 대화와 다른데 화면이 그대로 있으면 문이 거절하고(`session-kind-mismatch`), 목록은 눌리는데 안\n * 열리는 목록이 된다 — 선언은 있고 길이 없는 그 모양이다.\n *\n * 칸이 서기 전에 난 세션에는 값이 없다. 그때는 지금 대화로 둔다 — 서버가 다음 턴에 지금 대화로 찍는다.\n *\n * `undefined` 는 「그대로 둔다」는 뜻이다.\n */\nexport function followPickedConversation(pickedKind: string | undefined | null, currentKind: string | undefined): string | undefined {\n if (!pickedKind || pickedKind === currentKind) return undefined\n return pickedKind\n}\n"]}
|
|
@@ -1,85 +1,122 @@
|
|
|
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, followPickedConversation } 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
|
+
});
|
|
106
|
+
/*
|
|
107
|
+
* ADR-0072 결정 3 — 목록에서 고른 대화로 화면이 따라간다.
|
|
108
|
+
*/
|
|
109
|
+
describe('followPickedConversation', () => {
|
|
110
|
+
it('★ 다른 대화의 세션을 고르면 그 대화로 간다 — 안 따라가면 문이 거절한다', () => {
|
|
111
|
+
expect(followPickedConversation('figure.catalog', 'figure.modeller')).toBe('figure.catalog');
|
|
112
|
+
});
|
|
113
|
+
it('같은 대화면 그대로 둔다', () => {
|
|
114
|
+
expect(followPickedConversation('figure.modeller', 'figure.modeller')).toBeUndefined();
|
|
115
|
+
});
|
|
116
|
+
it('★ 칸이 서기 전 세션은 지금 대화로 둔다 — 서버가 다음 턴에 찍는다', () => {
|
|
117
|
+
expect(followPickedConversation(undefined, 'figure.modeller')).toBeUndefined();
|
|
118
|
+
expect(followPickedConversation(null, 'figure.modeller')).toBeUndefined();
|
|
119
|
+
expect(followPickedConversation('', 'figure.modeller')).toBeUndefined();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
85
122
|
//# 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":"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","sourcesContent":["/*\n * A mode is one value, and the point of it is that the parts of a conversation cannot\n * disagree with each other.\n *\n * The defect this guards against shipped: a screen whose systemPrompt said \"you do not edit\n * boards here\" offered `/add-monitor` in the same panel, because the prompt was an element\n * property and the slash list was a module global. Any resolution that lets a mode supply one\n * of them while something else supplies the other brings it back.\n */\nimport { isDifferentConversation, resolveConversation, type AssistantMode } from './assistant-mode'\n\nconst MODE: AssistantMode = {\n id: 'twin:space:S-12',\n systemPrompt: 'You are looking at one space. You do not edit boards here.',\n toolCategories: ['twin'],\n hostContext: { spaceId: 'S-12' },\n slashTemplates: [{ name: 'status', template: '이 공간의 지금 상태를 요약해줘' }]\n}\n\nconst HOST_PROPERTIES = {\n systemPrompt: 'a prompt the host set the old way',\n toolCategories: ['board-ai'],\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 footerNoticeKey: 'text.previewed-on-the-board'\n}\n\ndescribe('resolveConversation', () => {\n test('a mode answers before the host properties it overlaps', () => {\n const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS)\n expect(c.systemPrompt).toBe(MODE.systemPrompt)\n expect(c.toolCategories).toEqual(['twin'])\n expect(c.hostContext).toEqual({ spaceId: 'S-12' })\n })\n\n test('a mode answers before the global defaults — the prompt and the slash list cannot disagree', () => {\n const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS)\n expect(c.slashTemplates).toEqual(MODE.slashTemplates)\n /* A mode that says it does not edit boards must not offer a board edit. */\n expect(JSON.stringify(c.slashTemplates)).not.toContain('add-monitor')\n })\n\n test('★ what a mode leaves out stays out — the defaults another package registered do not fill it', () => {\n const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS)\n /* The board's `@` catalog and its \"previewed on the board\" line belong to board authoring. */\n expect(c.catalogEntries).toEqual([])\n expect(c.footerNoticeKey).toBeUndefined()\n })\n\n test('★ a mode that declares no slash list offers none, even when a board package registered its own', () => {\n /* plant's factory mode declares no `slashTemplates`; board-ai registers `/add-monitor` globally. */\n const { slashTemplates, ...withoutSlash } = MODE\n const c = resolveConversation({ ...withoutSlash, id: 'plant:factory:D-1' }, HOST_PROPERTIES, GLOBAL_DEFAULTS)\n expect(c.slashTemplates).toEqual([])\n })\n\n test('★ a mode that leaves out a field the host also set as a property does not take the property either', () => {\n const c = resolveConversation({ id: 'twin:none' }, HOST_PROPERTIES, GLOBAL_DEFAULTS)\n expect(c.systemPrompt).toBeUndefined()\n expect(c.toolCategories).toBeUndefined()\n expect(c.hostContext).toBeUndefined()\n })\n\n test('a host with no mode gets exactly what it had before modes existed', () => {\n const c = resolveConversation(undefined, HOST_PROPERTIES, GLOBAL_DEFAULTS)\n expect(c.systemPrompt).toBe(HOST_PROPERTIES.systemPrompt)\n expect(c.toolCategories).toEqual(HOST_PROPERTIES.toolCategories)\n expect(c.slashTemplates).toEqual(GLOBAL_DEFAULTS.slashTemplates)\n })\n\n test('nothing anywhere resolves to empty lists and the board endpoint, never undefined', () => {\n const c = resolveConversation(undefined, {}, {})\n expect(c.catalogEntries).toEqual([])\n expect(c.slashTemplates).toEqual([])\n expect(c.proposalChoices).toEqual([])\n expect(c.chatEndpoint).toBe('boardAIChat')\n })\n})\n\ndescribe('isDifferentConversation', () => {\n const station3: AssistantMode = { id: 'plant:station:3' }\n\n test('a different subject ends the conversation', () => {\n expect(isDifferentConversation(station3, { id: 'plant:station:7' })).toBe(true)\n expect(isDifferentConversation(undefined, station3)).toBe(true)\n })\n\n test('rewording the same subject does not', () => {\n expect(isDifferentConversation(station3, { ...station3, systemPrompt: 'reworded' })).toBe(false)\n expect(isDifferentConversation(undefined, undefined)).toBe(false)\n })\n})\n"]}
|
|
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, followPickedConversation } 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\n/*\n * ADR-0072 결정 3 — 목록에서 고른 대화로 화면이 따라간다.\n */\ndescribe('followPickedConversation', () => {\n it('★ 다른 대화의 세션을 고르면 그 대화로 간다 — 안 따라가면 문이 거절한다', () => {\n expect(followPickedConversation('figure.catalog', 'figure.modeller')).toBe('figure.catalog')\n })\n\n it('같은 대화면 그대로 둔다', () => {\n expect(followPickedConversation('figure.modeller', 'figure.modeller')).toBeUndefined()\n })\n\n it('★ 칸이 서기 전 세션은 지금 대화로 둔다 — 서버가 다음 턴에 찍는다', () => {\n expect(followPickedConversation(undefined, 'figure.modeller')).toBeUndefined()\n expect(followPickedConversation(null, 'figure.modeller')).toBeUndefined()\n expect(followPickedConversation('', 'figure.modeller')).toBeUndefined()\n })\n})\n"]}
|