@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/ai-assistant",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.29",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"things-factory": true,
|
|
6
6
|
"author": "heartyoh",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"@operato/graphql": "^10.0.0",
|
|
30
30
|
"@operato/i18n": "^10.0.0",
|
|
31
31
|
"@operato/styles": "^10.0.0",
|
|
32
|
-
"@things-factory/ai-client-base": "^10.1.
|
|
33
|
-
"@things-factory/auth-base": "^10.1.
|
|
32
|
+
"@things-factory/ai-client-base": "^10.1.29",
|
|
33
|
+
"@things-factory/auth-base": "^10.1.29",
|
|
34
34
|
"@things-factory/env": "^10.1.20",
|
|
35
|
-
"@things-factory/shell": "^10.1.
|
|
35
|
+
"@things-factory/shell": "^10.1.29",
|
|
36
36
|
"dompurify": "^3.0.0",
|
|
37
37
|
"graphql-scalars": "^1.22.0",
|
|
38
38
|
"graphql-tag": "^2.12.6",
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"copyfiles": "^2.4.1",
|
|
45
45
|
"rimraf": "^5.0.0"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "e6bf9dec003fe5e794746aaeb5852b3b50478232"
|
|
48
48
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ๋๊ตฌ๊ฐ ์ ์ด ๋ ๋ฌธ์ด ์คํค๋ง์ ์๋๊ฐ โ ADR-0072 ๊ฒฐ์ 5 ์ ํ๋ก ๋ฌป๋๋ค.
|
|
3
|
+
*
|
|
4
|
+
* ์คํ ํ๋๊ฐ ์กฐ์ฉํ๋ค. ADR-0073 ํ์ 1 ์ ๋ชจ๋ฅด๋ ๋ฌธ์ ๋ซ์ผ๋ฏ๋ก, ์ด๋ฆ์ ํ๋ฆฌ๊ฒ ์ ์ ๋๊ตฌ๋ก ๋ง๋ ๋ต์
|
|
5
|
+
* **์๋ฌด๋ ๋ชป ์ฝ๋๋ค** โ ์ด ์ฌ๋๊น์ง. ๋ถํ
์ด ๊ทธ๊ฒ์ ๋งํ์ง ์์ผ๋ฉด ํ๋ฉด์์ ์ฒ์ ๋ณธ๋ค.
|
|
6
|
+
*/
|
|
7
|
+
import { doorDeclarationWarnings, formatDoorDeclarationWarnings } from './door-declaration-report'
|
|
8
|
+
|
|
9
|
+
const table: Record<string, any> = {
|
|
10
|
+
'Query.jobOrders': { known: true, gate: { category: 'job-order', privilege: 'query', owner: false, super: false } },
|
|
11
|
+
'Query.twinExternalAttentions': { known: true, gate: null }
|
|
12
|
+
}
|
|
13
|
+
const lookup = (typeName: string, fieldName: string) => table[`${typeName}.${fieldName}`] ?? { known: false }
|
|
14
|
+
|
|
15
|
+
describe('declared doors, measured against the schema', () => {
|
|
16
|
+
it('โ
์คํค๋ง์ ์๋ ๋ฌธ์ ๋งํ๋ค โ ์คํ ํ๋๊ฐ ๊ทธ ๋ํ์ ๋ต์ ํต์งธ๋ก ๊ฐ๋ฆฐ๋ค', () => {
|
|
17
|
+
const found = doorDeclarationWarnings([{ name: 'findJobOrders', doors: ['Query.jobOrder'] }], lookup)
|
|
18
|
+
|
|
19
|
+
expect(found).toEqual([{ code: 'tool-door-not-in-schema', tool: 'findJobOrders', door: 'Query.jobOrder' }])
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('โ
์ ์ธ์ด ์๋ ๋ฌธ์ ์ฌ์ค๋ก ๋งํ๋ค โ ๊ทธ ๋ฌธ์ด ์ด๋ ค ์์ผ๋ ๋ต๋ ์ด๋ฆฐ๋ค', () => {
|
|
23
|
+
const found = doorDeclarationWarnings(
|
|
24
|
+
[{ name: 'listExternalAttentions', doors: ['Query.twinExternalAttentions'] }],
|
|
25
|
+
lookup
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
expect(found.map(w => w.code)).toEqual(['tool-door-has-no-privilege'])
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('์งํค๋ ๋ฌธ์๋ ์๋ฌด ๋ง๋ ํ์ง ์๋๋ค', () => {
|
|
32
|
+
expect(doorDeclarationWarnings([{ name: 'findJobOrders', doors: ['Query.jobOrders'] }], lookup)).toEqual([])
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('๋ฌธ์ ์ ์ ์ ๋๊ตฌ๋ ์ด ๊ฒ์ฌ์ ๊ฒ์ด ์๋๋ค โ ๋ฑ๋ก๊ธฐ๊ฐ ๋ฐ๋ก ๋งํ๋ค', () => {
|
|
36
|
+
expect(doorDeclarationWarnings([{ name: 'getOpenScene' }, { name: 'quiet', doors: [] }], lookup)).toEqual([])
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('โ
๋ฌธ ๋ชจ์์ด ์๋ ๊ธ์๋ ์คํค๋ง์ ์๋ ๊ฒ์ผ๋ก ์ผ๋ค', () => {
|
|
40
|
+
const found = doorDeclarationWarnings([{ name: 'odd', doors: ['jobOrders'] }], lookup)
|
|
41
|
+
|
|
42
|
+
expect(found.map(w => w.code)).toEqual(['tool-door-not-in-schema'])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('์ฝ๋๋ง๋ค ํ ์ค๋ก ๋ชจ์๋ค', () => {
|
|
46
|
+
const lines = formatDoorDeclarationWarnings([
|
|
47
|
+
{ code: 'tool-door-not-in-schema', tool: 'a', door: 'Query.x' },
|
|
48
|
+
{ code: 'tool-door-not-in-schema', tool: 'b', door: 'Query.y' }
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
expect(lines).toEqual(['[ai-assistant] tool-door-not-in-schema ร2: a โ Query.x, b โ Query.y'])
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ๋๊ตฌ๊ฐ ์ ์ด ๋ ๋ฌธ์ด **์คํค๋ง์ ์๋ ๋ฌธ์ธ๊ฐ** โ ๋ถํ
์์ ํ ๋ฒ ๋ฌป๋๋ค.
|
|
3
|
+
*
|
|
4
|
+
* โโ ์ ๋ฑ๋ก๊ธฐ๊ฐ ์๋๋ผ ์ฌ๊ธฐ์ธ๊ฐ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
5
|
+
* ์ด ๋ฌผ์์ AUTH ์ ํ๋โ๊ถํ ํ(`privilegeOfField`, ADR-0072 ๊ฒฐ์ 5)๊ฐ ์์ด์ผ ๋ตํ ์ ์๊ณ , ๊ทธ ํ๋
|
|
6
|
+
* **์คํค๋ง๋ฅผ ์ง์ ๋ค์** ์ฐฌ๋ค. ๋ฑ๋ก๊ธฐ๋ ๋ชจ๋์ด ์ฌ๋ผ์ค๋ ๋์ ๋๋ ๊ทธ๋๋ ํ๊ฐ ๋น์ด ์๋ค. ๋ถํ
๋ณด๊ณ ๋
|
|
7
|
+
* ๋ชจ๋ ๋ชจ๋์ด ๋ฑ๋ก์ ๋ง์น๊ณ ์คํค๋ง๊ฐ ์ ๋ค์ ๋๋ ์๋ฆฌ๋ค.
|
|
8
|
+
*
|
|
9
|
+
* โโ ๋ฌด์์ ๊ฐ๋ฅด๋ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
10
|
+
* `tool-door-not-in-schema` ๊ทธ๋ฐ ๋ฃจํธ ํ๋๊ฐ ์๋ค. ๋๊ตฌ๊ฐ ์ด๋ฆ์ ํ๋ฆฌ๊ฒ ์ ์ ๊ฒ์ด๋ค โ ๊ทธ ๋๊ตฌ๋ก
|
|
11
|
+
* ๋ง๋ ๋ต์ **์๋ฌด๋ ๋ชป ์ฝ๋๋ค**(ADR-0073 ํ์ 1 ์ ๋ชจ๋ฅด๋ ๋ฌธ์ ๋ซ๋๋ค).
|
|
12
|
+
* ์คํ ํ๋๊ฐ ๊ทธ ๋ํ์ ๋ต์ ํต์งธ๋ก ๊ฐ๋ฆฐ๋ค.
|
|
13
|
+
* `tool-door-has-no-privilege` ํ๋๋ ์๊ณ ์๋ฌด๊ฒ๋ ์ ์ธํ์ง ์์๋ค. ๊ทธ ๋ฌธ์ด ์ด๋ ค ์์ผ๋ฏ๋ก ๊ทธ ๋ต๋
|
|
14
|
+
* ๋๋ฉ์ธ ์์์ ์ด๋ฆฐ๋ค. ๊ฒฐํจ์ด ์๋๋ผ **์ฌ์ค**์ด๊ณ , ๋ฌธ์ ์ฐจ๋ก๋ค.
|
|
15
|
+
*
|
|
16
|
+
* ๋ฑ๋ก๊ธฐ์ ๊ฐ์ ์ด๋ฆ ๊ฒฝ๊ณ ์ ๋ฌป๋ ๊ฒ์ด ๋ค๋ฅด๋ค. ๋ฑ๋ก๊ธฐ๋ ใ๋๊ตฌ๊ฐ ๋ฌธ์ ๊ถํ์ ๋ฒ ๊ปด ์ ์๋ใ๋ฅผ ๋ฌผ์๊ณ ,
|
|
17
|
+
* ๊ทธ ๋ฒ ๋ผ๊ธฐ๋ ํ๊ฐ ์๋ฉด์ ์์ด์ง๋ค(ADR-0072 ๊ฒฐ์ 5). ์ฌ๊ธฐ์๋ **๋ฌธ์ ์ง์ ** ๋ฌป๋๋ค.
|
|
18
|
+
*/
|
|
19
|
+
import { getAllToolSpecs } from '@things-factory/ai-client-base'
|
|
20
|
+
import { privilegeOfField } from '@things-factory/auth-base'
|
|
21
|
+
|
|
22
|
+
export interface DoorDeclarationWarning {
|
|
23
|
+
code: 'tool-door-not-in-schema' | 'tool-door-has-no-privilege'
|
|
24
|
+
tool: string
|
|
25
|
+
door: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** ๋ฑ๋ก๋ ๋๊ตฌ๋ค์ด ์ ์ด ๋ ๋ฌธ์ ์คํค๋ง์ ๋์ด ๋ณธ๋ค. ์คํค๋ง๊ฐ ์ ๋ค์๋ง ๋ป์ด ์๋ค. */
|
|
29
|
+
export function doorDeclarationWarnings(
|
|
30
|
+
specs: ReadonlyArray<any> = getAllToolSpecs(),
|
|
31
|
+
lookup: (typeName: string, fieldName: string) => { known: false } | { known: true; gate: unknown } = privilegeOfField
|
|
32
|
+
): DoorDeclarationWarning[] {
|
|
33
|
+
const out: DoorDeclarationWarning[] = []
|
|
34
|
+
|
|
35
|
+
for (const spec of specs) {
|
|
36
|
+
for (const door of spec?.doors ?? []) {
|
|
37
|
+
const match = /^(Query|Mutation|Subscription)\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(String(door))
|
|
38
|
+
if (!match) {
|
|
39
|
+
out.push({ code: 'tool-door-not-in-schema', tool: spec.name, door: String(door) })
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const asked = lookup(match[1], match[2])
|
|
44
|
+
if (!asked.known) out.push({ code: 'tool-door-not-in-schema', tool: spec.name, door })
|
|
45
|
+
else if (asked.gate === null) out.push({ code: 'tool-door-has-no-privilege', tool: spec.name, door })
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return out
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** ์ฝ๋๋ง๋ค ํ ์ค โ ๋ช์ด๊ณ ์ด๋ ๋๊ตฌ์ ์ด๋ ๋ฌธ์ธ๊ฐ. */
|
|
53
|
+
export function formatDoorDeclarationWarnings(warnings: ReadonlyArray<DoorDeclarationWarning>): string[] {
|
|
54
|
+
const byCode = new Map<string, string[]>()
|
|
55
|
+
for (const warning of warnings) {
|
|
56
|
+
const found = byCode.get(warning.code) ?? []
|
|
57
|
+
found.push(`${warning.tool} โ ${warning.door}`)
|
|
58
|
+
byCode.set(warning.code, found)
|
|
59
|
+
}
|
|
60
|
+
return [...byCode].map(([code, found]) => `[ai-assistant] ${code} ร${found.length}: ${found.join(', ')}`)
|
|
61
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ๋ชจ๋ ๋ฌธ์ด **์๊ธฐ ํ์ ๋ช
์ํ๋๊ฐ** โ ๋น ์ง๋ฉด ๋ถํ
์ด ์ฃฝ๋๋ค.
|
|
3
|
+
*
|
|
4
|
+
* โโ ์ ์ด ์ํ์ด ์๋ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
5
|
+
* `chatMessageActivity` ๋ฅผ async ๋ก ๋ฐ๊พธ์(๋ฐ๋ ์ฌ๋๋ง๋ค ๊ฐ๋ฆฌ๊ธฐ โ ADR-0073 ํ์ 1) ๋ฐํํ์ด
|
|
6
|
+
* `Promise<ChatMessage>` ๊ฐ ๋์๊ณ , type-graphql ์ reflection ์ Promise ์์ ๋ชป ๋ณธ๋ค. ์คํค๋ง๋ฅผ
|
|
7
|
+
* ์ธ์ธ ๋ NoExplicitTypeError ๊ฐ ๋๊ณ **ํจํค์ง ์ ์ฌ ์์ฒด๊ฐ ์คํจํ๋ค** โ ์ํ์ด ์๋๋ผ ์๋ฒ๊ฐ ์ฃฝ๋๋ค.
|
|
8
|
+
* ์ปดํ์ผ์ ํต๊ณผํ๋ฏ๋ก ํ์
๊ฒ์ฌ๋ ๋ชป ์ก๋๋ค. plant ๋ ์ธ์ด ์ค์น๋ณธ์ ์ฌ๋ฆฌ๋ค ์ก์ ์๋ ค ์ฃผ์๋ค(2026-09-18).
|
|
9
|
+
*
|
|
10
|
+
* โโ ์ ์คํค๋ง๋ฅผ ์ ์ธ์ฐ๊ณ ๊ธ์๋ฅผ ์ฝ๋ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
11
|
+
* ์ด ๊พธ๋ฌ๋ฏธ์ ์ํ๊ธฐ๋ babel ์ด๋ผ ๋ฐ์ฝ๋ ์ดํฐ metadata ๊ฐ ์๋ค. ์คํค๋ง๋ฅผ ์ธ์ฐ๋ฉด ๋ฆฌํ๋ ์
์ด ์์ด ์ ๋ถ
|
|
12
|
+
* ๊ฐ์ ์ค๋ฅ๊ฐ ๋๊ณ , ๊ทธ๋ฌ๋ฉด ์ด ์ํ์ ์๋ฌด๊ฒ๋ ๋ชป ๊ฐ๋ฅธ๋ค. ๋์ ์ ์ธ์ ์ฝ๋๋ค โ ๋ฐ์ฝ๋ ์ดํฐ๊ฐ ํ์
|
|
13
|
+
* ์ธ์๋ก ๋ฐ๊ณ ์๋์ง๋ง ๋ณธ๋ค. ๊ทธ๊ฒ์ด ๋น ์ง ์๋ฆฌ๊ฐ ์ ํํ ํฐ์ง๋ ์๋ฆฌ๋ค.
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync, readdirSync } from 'node:fs'
|
|
16
|
+
import { join } from 'node:path'
|
|
17
|
+
|
|
18
|
+
const root = join(__dirname, 'service')
|
|
19
|
+
|
|
20
|
+
function sources(dir: string): { path: string; text: string }[] {
|
|
21
|
+
const out: { path: string; text: string }[] = []
|
|
22
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
23
|
+
const path = join(dir, entry.name)
|
|
24
|
+
if (entry.isDirectory()) out.push(...sources(path))
|
|
25
|
+
else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.test.ts')) {
|
|
26
|
+
out.push({ path, text: readFileSync(path, 'utf-8') })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return out
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** `@Query(`, `@Mutation(`, `@Subscription(` ๋ฐ๋ก ๋ค์ ๊ธ์๊ฐ ํ์ ์ฃผ๊ณ ์๋. */
|
|
33
|
+
function untyped(): string[] {
|
|
34
|
+
const out: string[] = []
|
|
35
|
+
for (const { path, text } of sources(root)) {
|
|
36
|
+
for (const match of text.matchAll(/@(Query|Mutation|Subscription)\(\s*([^\n]*)/g)) {
|
|
37
|
+
const [, kind, rest] = match
|
|
38
|
+
/* ํ์ ์ฃผ๋ ๋ ๋ชจ์: `() => X` ์ `type => X`. ๊ทธ ๋ฐ์ ์ต์
๊ฐ์ฒด๋ถํฐ ์์ํ ๊ฒ์ด๋ค. */
|
|
39
|
+
if (/^\(\s*\)\s*=>/.test(rest) || /^\w+\s*=>/.test(rest)) continue
|
|
40
|
+
const line = text.slice(0, match.index).split('\n').length
|
|
41
|
+
out.push(`${path.slice(root.length + 1)}:${line} @${kind}`)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe('every GraphQL door names its type', () => {
|
|
48
|
+
it('โ
ํ์ ์ ์ค Query/Mutation/Subscription ์ด ์๋ค โ ์์ผ๋ฉด ๋ถํ
์์ ํฐ์ง๋ค', () => {
|
|
49
|
+
expect(untyped()).toEqual([])
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('์ด ์ํ์ด ์ค์ ๋ก ๋ฌธ๋ค์ ๋ณด๊ณ ์๋ค โ ์ ๊ท์์ด ์์ค์ ์ด๊ธ๋๋ฉด 0๊ฑด์ผ๋ก ์กฐ์ฉํ ํต๊ณผํ๋ค', () => {
|
|
53
|
+
const doors = sources(root).flatMap(s => [...s.text.matchAll(/@(Query|Mutation|Subscription)\(/g)])
|
|
54
|
+
expect(doors.length).toBeGreaterThan(20)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* One window for one question: what state is this deployment's AI in.
|
|
3
|
+
*
|
|
4
|
+
* Two things were being answered by failure or by a log line โ whether a provider is configured at
|
|
5
|
+
* all (standard A7-a), and which registrations are not yet what the contracts ask for (A1 stage one,
|
|
6
|
+
* and the modes' `/` sentences). They are the same question asked by different people, so they come
|
|
7
|
+
* back together, as codes.
|
|
8
|
+
*
|
|
9
|
+
* A screen reads this **before** offering to ask the assistant. It is a query: reading what a
|
|
10
|
+
* deployment can do is reading.
|
|
11
|
+
*/
|
|
12
|
+
import { Ctx, Directive, Field, ObjectType, Query, Resolver } from 'type-graphql'
|
|
13
|
+
import { GraphQLJSON } from 'graphql-scalars'
|
|
14
|
+
import '@things-factory/auth-base'
|
|
15
|
+
|
|
16
|
+
import { aiCapability, getToolRegistryWarnings } from '@things-factory/ai-client-base'
|
|
17
|
+
|
|
18
|
+
import { getAssistantModeWarnings } from './assistant-mode/index.js'
|
|
19
|
+
|
|
20
|
+
@ObjectType({ description: 'A registration that was accepted but is not yet what the contract asks for.' })
|
|
21
|
+
class AiRegistrationWarning {
|
|
22
|
+
@Field({ description: 'What is missing, as a code โ never prose a screen has to parse.' })
|
|
23
|
+
code: string
|
|
24
|
+
|
|
25
|
+
@Field(() => GraphQLJSON, { description: 'Which registration it is about (category, tool, mode, slash).' })
|
|
26
|
+
params: any
|
|
27
|
+
|
|
28
|
+
@Field({ description: 'The same thing in a sentence, for whoever reads a boot log.' })
|
|
29
|
+
message: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@ObjectType({ description: "Whether this deployment has an AI, and what its registrations are missing." })
|
|
33
|
+
class AiCapabilityView {
|
|
34
|
+
@Field({ description: "'configured' or 'not-configured'. A screen that has not asked yet knows a third state of its own: it does not know." })
|
|
35
|
+
state: string
|
|
36
|
+
|
|
37
|
+
@Field({ nullable: true, description: 'Which provider answers.' })
|
|
38
|
+
provider?: string
|
|
39
|
+
|
|
40
|
+
@Field({ nullable: true, description: 'The default model this deployment would use.' })
|
|
41
|
+
model?: string
|
|
42
|
+
|
|
43
|
+
@Field({ nullable: true, description: 'Why it is not configured: no-config ยท no-provider ยท unknown-provider ยท no-adapter.' })
|
|
44
|
+
reason?: string
|
|
45
|
+
|
|
46
|
+
@Field(() => [AiRegistrationWarning], { description: 'Tools with no declared privilege, and modes offering work their tools cannot do.' })
|
|
47
|
+
warnings: AiRegistrationWarning[]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Resolver()
|
|
51
|
+
export class AssistantCapabilityResolver {
|
|
52
|
+
@Query(() => AiCapabilityView, {
|
|
53
|
+
description: 'Whether this deployment has an AI configured, and which registrations are incomplete. Read this before offering to ask the assistant โ do not find out by failing.'
|
|
54
|
+
})
|
|
55
|
+
@Directive('@privilege(category: "ai-assistant", privilege: "query")')
|
|
56
|
+
async aiCapability(@Ctx() _context: any): Promise<AiCapabilityView> {
|
|
57
|
+
const capability = aiCapability()
|
|
58
|
+
const warnings = [...getToolRegistryWarnings(), ...getAssistantModeWarnings()].map(warning => ({
|
|
59
|
+
code: warning.code,
|
|
60
|
+
params: warning.params,
|
|
61
|
+
message: warning.message
|
|
62
|
+
}))
|
|
63
|
+
|
|
64
|
+
return { ...capability, warnings } as AiCapabilityView
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -28,16 +28,33 @@ import '@things-factory/auth-base'
|
|
|
28
28
|
|
|
29
29
|
import { config } from '@things-factory/env'
|
|
30
30
|
import { getRepository } from '@things-factory/shell'
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
createAIClient,
|
|
33
|
+
findToolSpec,
|
|
34
|
+
getDefaultAIClient,
|
|
35
|
+
registryAssistantChat,
|
|
36
|
+
type AIClient
|
|
37
|
+
} from '@things-factory/ai-client-base'
|
|
32
38
|
|
|
33
39
|
import { ChatSession } from './chat-session/chat-session.js'
|
|
34
40
|
import { assertStationSession } from './chat-session/station-session-guard.js'
|
|
35
41
|
import { ChatMessage } from './chat-message/chat-message.js'
|
|
36
42
|
import { buildLlmHistory } from './chat-message/llm-history.js'
|
|
43
|
+
import { splitByCap } from './chat-message/history-summary.js'
|
|
44
|
+
import { foldSessionHistory } from './chat-message/fold-session-history.js'
|
|
45
|
+
import { groundingSourcesFor } from './grounding-sources.js'
|
|
37
46
|
import { publishChatMessage } from './chat-message/chat-message-publish.js'
|
|
38
47
|
import { publishSessionActivity } from './chat-session/session-activity-publish.js'
|
|
39
48
|
import { activityPatch, autoSessionName } from './chat-session/session-inbox.js'
|
|
40
49
|
import { toolAccessFor } from './tool-access.js'
|
|
50
|
+
import { ChatSessionParticipant } from './chat-session-participant/chat-session-participant.js'
|
|
51
|
+
import { doorsForAnswer } from './chat-message/message-doors.js'
|
|
52
|
+
import { judgeSessionKind } from './chat-session/session-kind.js'
|
|
53
|
+
import { readerGate } from './chat-message/reader-gate.js'
|
|
54
|
+
import { inviteMentionedUsers } from './chat-session-participant/mention-invite.js'
|
|
55
|
+
import { buildInviteNotification } from './chat-session-participant/invite-notification.js'
|
|
56
|
+
import { resolveModeSettings } from './assistant-mode/resolve.js'
|
|
57
|
+
import { requestTranslator } from './request-translator.js'
|
|
41
58
|
|
|
42
59
|
/**
|
|
43
60
|
* How many stored messages reach the model.
|
|
@@ -57,6 +74,19 @@ class AssistantMessageInput {
|
|
|
57
74
|
content: string
|
|
58
75
|
}
|
|
59
76
|
|
|
77
|
+
@InputType({ description: 'Which conversation this is, and what it is about.' })
|
|
78
|
+
class AssistantModeInput {
|
|
79
|
+
@Field({ description: 'A mode kind the app server registered โ `<app>.<surface>`, e.g. "twin.space".' })
|
|
80
|
+
kind: string
|
|
81
|
+
|
|
82
|
+
@Field({
|
|
83
|
+
nullable: true,
|
|
84
|
+
description:
|
|
85
|
+
'What this conversation is about โ the space, the station, the model being edited. With `kind` it is the conversation\'s lifetime: when either changes the screen closes the conversation and starts another.'
|
|
86
|
+
})
|
|
87
|
+
target?: string
|
|
88
|
+
}
|
|
89
|
+
|
|
60
90
|
@InputType({ description: 'One turn of a conversation that has no document of its own.' })
|
|
61
91
|
class AssistantChatInput {
|
|
62
92
|
@Field({
|
|
@@ -71,18 +101,11 @@ class AssistantChatInput {
|
|
|
71
101
|
})
|
|
72
102
|
messages: AssistantMessageInput[]
|
|
73
103
|
|
|
74
|
-
@Field({
|
|
75
|
-
description:
|
|
76
|
-
'Who the assistant is on this surface, in the words of the product that owns the screen. The rules for using each tool come from whoever registered it and are appended by the server.'
|
|
77
|
-
})
|
|
78
|
-
systemPrompt: string
|
|
79
|
-
|
|
80
|
-
@Field(() => [String], {
|
|
81
|
-
nullable: true,
|
|
104
|
+
@Field(() => AssistantModeInput, {
|
|
82
105
|
description:
|
|
83
|
-
'
|
|
106
|
+
'Which conversation this is. The persona, the tool categories, the model and the limits come from the mode the app server registered (ADR-0061 decision 7) โ a caller choosing its own tool categories would be a caller choosing its own authority.'
|
|
84
107
|
})
|
|
85
|
-
|
|
108
|
+
mode: AssistantModeInput
|
|
86
109
|
|
|
87
110
|
@Field(() => GraphQLJSON, {
|
|
88
111
|
nullable: true,
|
|
@@ -98,21 +121,6 @@ class AssistantChatInput {
|
|
|
98
121
|
})
|
|
99
122
|
truncateAfterMessageId?: string
|
|
100
123
|
|
|
101
|
-
@Field({
|
|
102
|
-
nullable: true,
|
|
103
|
-
description:
|
|
104
|
-
'Require a tool call before the model may answer. For surfaces that report live state, where an unchecked assertion reads the same as a checked one.'
|
|
105
|
-
})
|
|
106
|
-
requireGroundingTools?: boolean
|
|
107
|
-
|
|
108
|
-
@Field({ nullable: true, description: 'Model identifier override. Falls back to the configured default.' })
|
|
109
|
-
model?: string
|
|
110
|
-
|
|
111
|
-
@Field({ nullable: true, description: 'Sampling temperature override.' })
|
|
112
|
-
temperature?: number
|
|
113
|
-
|
|
114
|
-
@Field({ nullable: true, description: 'Max output tokens per call.' })
|
|
115
|
-
maxTokens?: number
|
|
116
124
|
}
|
|
117
125
|
|
|
118
126
|
@ObjectType()
|
|
@@ -132,6 +140,13 @@ class AssistantChatOutput {
|
|
|
132
140
|
@Field({ nullable: true, description: 'Stored ChatMessage id of the reply.' })
|
|
133
141
|
assistantMessageId?: string
|
|
134
142
|
|
|
143
|
+
@Field(() => GraphQLJSON, {
|
|
144
|
+
nullable: true,
|
|
145
|
+
description:
|
|
146
|
+
'Set when this turn did not fit: { omitted, summarized } โ how many earlier messages the model did not see, and whether a summary stood in for them. Without it the person watches the conversation lose its beginning in silence, while the model is told.'
|
|
147
|
+
})
|
|
148
|
+
historyFolded?: any
|
|
149
|
+
|
|
135
150
|
@Field(() => GraphQLJSON, {
|
|
136
151
|
nullable: true,
|
|
137
152
|
description: 'Tool usages during the turn โ {name, arguments, result, kind}. The surface shows them foldably.'
|
|
@@ -194,6 +209,14 @@ export class AssistantChatResolver {
|
|
|
194
209
|
)
|
|
195
210
|
}
|
|
196
211
|
|
|
212
|
+
/*
|
|
213
|
+
* Read the declaration before anything is stored. A kind this server does not have is a
|
|
214
|
+
* mismatched deployment, and the person should see that instead of a conversation with no
|
|
215
|
+
* persona and no tools.
|
|
216
|
+
*/
|
|
217
|
+
const t = requestTranslator(context)
|
|
218
|
+
const settings = resolveModeSettings(input.mode, t)
|
|
219
|
+
|
|
197
220
|
const { domain, user } = context.state
|
|
198
221
|
const sessionRepo = getRepository(ChatSession)
|
|
199
222
|
const messageRepo = getRepository(ChatMessage)
|
|
@@ -203,15 +226,41 @@ export class AssistantChatResolver {
|
|
|
203
226
|
const found = await sessionRepo.findOneBy({ id: input.sessionId, domain: { id: domain.id } as any })
|
|
204
227
|
if (!found) throw new Error(`ChatSession ${input.sessionId} not found`)
|
|
205
228
|
if (found.anchorType === 'figure' && input.hostContext?.figureId !== found.anchorId) {
|
|
206
|
-
throw new Error(
|
|
229
|
+
throw new Error(
|
|
230
|
+
context.t('ai-assistant.text.other-figure-history', {
|
|
231
|
+
defaultValue: 'this conversation belongs to another figure. open the conversation of the model you have open.'
|
|
232
|
+
})
|
|
233
|
+
)
|
|
207
234
|
}
|
|
208
235
|
if (found.anchorType === 'application' && (input.hostContext?.app !== found.anchorId || input.hostContext?.figureId)) {
|
|
209
|
-
throw new Error(
|
|
236
|
+
throw new Error(
|
|
237
|
+
context.t('ai-assistant.text.application-vs-model-history', {
|
|
238
|
+
defaultValue: 'this conversation is about the application, not about a model. open the one you want.'
|
|
239
|
+
})
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
/*
|
|
243
|
+
* ์ด ์ธ์
์ด **์ด ๋ํ์** ์ธ์
์ธ๊ฐ โ ADR-0072 ๊ฒฐ์ 3. ๋ค๋ฅธ ๋ํ์ ์ธ์
์ ๋ง์ ๋ถ์ด๋ฉด ๊ทธ ์ด๋ ฅ์ด
|
|
244
|
+
* ์ด ๋ํ์ ํ๋กฌํํธ๋ก ๋ค์ด๊ฐ๊ณ , ํ๋ฉด์ ์ค์ด ์ฌ๋ผ์ ธ ์ ๋ํ์ฒ๋ผ ๋ณด์ด๋ฏ๋ก ๋ณด๋ธ ์ฌ๋์ ๋ชจ๋ฅธ๋ค.
|
|
245
|
+
*/
|
|
246
|
+
const verdict = judgeSessionKind((found as any).kind, input.mode.kind)
|
|
247
|
+
if (verdict.action === 'refuse') {
|
|
248
|
+
throw new Error(
|
|
249
|
+
t('ai-assistant.text.session-kind-mismatch', {
|
|
250
|
+
...verdict.params,
|
|
251
|
+
defaultValue: 'this conversation is {sessionKind}, and you are writing as {requested}. open the conversation you mean.'
|
|
252
|
+
})
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
if (verdict.action === 'stamp') {
|
|
256
|
+
/* ์นธ์ด ์๊ธฐ ์ ์ ๋ ์ธ์
โ ์์ฒญํ ๋ํ๋ก ํ ๋ฒ ์ฐ๋๋ค. ๋ค์๋ถํฐ๋ ์ ๊ท์น ํ๋๋ง ๋จ๋๋ค. */
|
|
257
|
+
await sessionRepo.update(found.id!, { kind: verdict.kind } as any)
|
|
258
|
+
;(found as any).kind = verdict.kind
|
|
210
259
|
}
|
|
211
260
|
session = found
|
|
212
261
|
}
|
|
213
262
|
|
|
214
|
-
assertStationSession(session, input.hostContext)
|
|
263
|
+
assertStationSession(session, input.hostContext, t)
|
|
215
264
|
|
|
216
265
|
const lastUserMessage = input.messages[input.messages.length - 1]
|
|
217
266
|
let userMessageId: string | undefined
|
|
@@ -225,6 +274,12 @@ export class AssistantChatResolver {
|
|
|
225
274
|
session: { id: session.id } as any,
|
|
226
275
|
role: 'user',
|
|
227
276
|
content: lastUserMessage.content,
|
|
277
|
+
/*
|
|
278
|
+
* This door is the one people call to ask the assistant, so a turn stored here was for the
|
|
279
|
+
* assistant (ADR-0070 A15 decision โ ). The value comes from which door was called, never
|
|
280
|
+
* from the request โ two sources for one fact can disagree.
|
|
281
|
+
*/
|
|
282
|
+
addressee: 'assistant',
|
|
228
283
|
creator: user,
|
|
229
284
|
updater: user,
|
|
230
285
|
parentMessage: previousLast ? ({ id: previousLast.id } as any) : undefined
|
|
@@ -244,6 +299,27 @@ export class AssistantChatResolver {
|
|
|
244
299
|
/* noop */
|
|
245
300
|
}
|
|
246
301
|
|
|
302
|
+
/*
|
|
303
|
+
* ์ฌ๋์ด `@์ด๋ฆ` ์ผ๋ก ๋ถ๋ฅธ ์ฌ๋์ ์ด ๋ํ์ ๋ค์ด์จ๋ค(ADR-0071 ๊ฒฐ์ 1). ์ด ๋ฌธ์ **์ฌ๋์ ํด**์
|
|
304
|
+
* ์ ์ฅํ๋ ๋ฌธ์ด๋ผ ๊ทธ ์ผ์ ํ๋ค โ ๋ชจ๋ธ์ ๋ต์ด๋ ๋๊ตฌ ๊ฒฐ๊ณผ๋ ์๋ฌด๋ ์ด๋ํ์ง ์๋๋ค.
|
|
305
|
+
*/
|
|
306
|
+
const invite = await inviteMentionedUsers({
|
|
307
|
+
content: lastUserMessage.content,
|
|
308
|
+
sessionId: session.id!,
|
|
309
|
+
domainId: domain.id,
|
|
310
|
+
inviter: user,
|
|
311
|
+
participants: getRepository(ChatSessionParticipant),
|
|
312
|
+
visibility: (session as any).visibility
|
|
313
|
+
})
|
|
314
|
+
if (invite.invited.length > 0) {
|
|
315
|
+
const payload = buildInviteNotification(invite.invited, undefined, user)
|
|
316
|
+
try {
|
|
317
|
+
if (payload) await (context.state as any)?.notify?.(payload)
|
|
318
|
+
} catch {
|
|
319
|
+
/* ํธ์ง๊ฐ ๋ชป ๊ฐ๋ค๊ณ ๋ฐฉ์ ๋ค์ด์จ ์ฌ์ค์ด ์ทจ์๋์ง๋ ์๋๋ค. */
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
247
323
|
publishChatMessage({ domainId: domain.id, sessionId: session.id, message: saved } as any)
|
|
248
324
|
publishSessionActivity({
|
|
249
325
|
domainId: domain.id,
|
|
@@ -256,13 +332,52 @@ export class AssistantChatResolver {
|
|
|
256
332
|
})
|
|
257
333
|
}
|
|
258
334
|
|
|
335
|
+
/*
|
|
336
|
+
* ์ด๋ฒ ํด์ ์ํ ๋ฐ์ผ๋ก ๋ฐ๋ ค๋ ๊ฒ์ด ์๋๊ฐ โ ํ๋ฉด์ด ใ๋ํ๊ฐ ๊ธธ์ด์ก๋คใ๋ฅผ ์๋ฆด ๊ทผ๊ฑฐ๋ค.
|
|
337
|
+
*
|
|
338
|
+
* ์ด ๋ฌธ์ ๊ทธ ์ฌ์ค์ ๋ด์ง ์์๋ค. ๋ํ์ฐฝ์๋ ๊ทธ ์๋ด๋ฅผ ๊ทธ๋ฆฌ๋ ์๋ฆฌ๊ฐ ์๊ณ (`historyFolded`), ๋ณด๋
|
|
339
|
+
* ๋ฌธ๋ง ๊ทธ ๊ฐ์ ์ฑ์ ๋ค. ๊ทธ๋์ ์ฑ์ ๋ํ๋ ์๋ถ๋ถ์ด ์๋ ค ๋๊ฐ๋ **์ฌ๋์๊ฒ๋ ์๋ฌด ๋ง๋ ํ์ง ์์๋ค** โ
|
|
340
|
+
* ๋ชจ๋ธ์๊ฒ๋ ใN ๊ฐ ์๋ตใ์ ์ ์ด ๋ณด๋ด๋ฉด์.
|
|
341
|
+
*/
|
|
342
|
+
const readRows = session
|
|
343
|
+
? await messageRepo.find({
|
|
344
|
+
where: { session: { id: session.id } as any },
|
|
345
|
+
order: { createdAt: 'ASC' },
|
|
346
|
+
relations: ['creator']
|
|
347
|
+
})
|
|
348
|
+
: []
|
|
349
|
+
/*
|
|
350
|
+
* ๋ชจ๋ธ์ด ์ฝ๋ ์ด๋ ฅ๋ **๋ถ๋ฅธ ์ฌ๋** ๊ธฐ์ค์ผ๋ก ๊ฐ๋ฆฐ๋ค โ ADR-0073 ํ์ 1. ๊ฐ๋ฆฌ์ง ์์ผ๋ฉด, ๋ฌธ์ ํ ๋ฒ๋
|
|
351
|
+
* ์ ์ง๋ ์ฌ๋์ด "์๊น ๊ทธ ์ ๋ค์ ๋งํด ์ค" ํ ๋ง๋๋ก ๊ฐ์ ๊ฐ์ ธ๊ฐ๋ค. ๋ชจ๋ธ์ด ์ฎ๊ฒจ ์ ๋ ์๊ฐ ๊ทธ ๊ฐ์
|
|
352
|
+
* ๋ฌธ ์๋ ์ ๋ต์ด ๋๋ค. ๊ฐ๋ฆฐ ์๋ฆฌ๋ฅผ ์ง์ฐ์ง ์๊ณ ํ ์ค๋ก ๋จ๊ธฐ๋ ์ด์ ๋ ๊ฐ๋ค โ ์ง์ฐ๋ฉด ๋ชจ๋ธ์ด ๊ทธ๋ฐ
|
|
353
|
+
* ๋ง์ด ์์๋ค๊ณ ๋ตํ๊ณ , ๊ทธ๊ฒ์ ๊ฑฐ์ง๋ง์ด๋ค.
|
|
354
|
+
*/
|
|
355
|
+
const storedRows = await readerGate(context)(readRows)
|
|
356
|
+
const capped = splitByCap(
|
|
357
|
+
storedRows.map(row => ({ id: row.id, role: row.role, content: row.content ?? '' })),
|
|
358
|
+
LLM_HISTORY_MAX_TURNS
|
|
359
|
+
)
|
|
360
|
+
const historyFolded =
|
|
361
|
+
capped.dropped.length > 0
|
|
362
|
+
? { omitted: capped.dropped.length, summarized: Boolean((session as any)?.lastSummary) }
|
|
363
|
+
: undefined
|
|
364
|
+
|
|
259
365
|
const conversation = session
|
|
260
366
|
? buildLlmHistory(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
367
|
+
/*
|
|
368
|
+
* The model reads who spoke and who they spoke to. The entity carries the speaker as a
|
|
369
|
+
* relation and the addressee as a column; the history wants them as plain fields, and
|
|
370
|
+
* without this mapping every line arrived unattributed โ a room of people read as one
|
|
371
|
+
* voice (ADR-0070 A15 decision โก).
|
|
372
|
+
*/
|
|
373
|
+
storedRows.map(message => ({
|
|
374
|
+
id: message.id,
|
|
375
|
+
role: message.role,
|
|
376
|
+
content: message.content,
|
|
377
|
+
senderId: (message as any).creator?.id,
|
|
378
|
+
senderName: (message as any).creator?.name,
|
|
379
|
+
addressee: (message as any).addressee
|
|
380
|
+
})),
|
|
266
381
|
{
|
|
267
382
|
maxTurns: LLM_HISTORY_MAX_TURNS,
|
|
268
383
|
truncateAfterMessageId: input.truncateAfterMessageId,
|
|
@@ -291,23 +406,53 @@ export class AssistantChatResolver {
|
|
|
291
406
|
const result = await registryAssistantChat({
|
|
292
407
|
client,
|
|
293
408
|
messages: conversation as any,
|
|
294
|
-
systemPrompt:
|
|
295
|
-
toolCategories:
|
|
409
|
+
systemPrompt: settings.systemPrompt,
|
|
410
|
+
toolCategories: settings.toolCategories,
|
|
411
|
+
/* ๋๊ตฌ๊ฐ ใ์ด๋ ๋ํ์์ ๋ถ๋ ธ๋ใ๋ฅผ ์๋ค โ ์๋ฒ์ ๊ฐ์ด๋ค(ADR-0061 ๊ฒฐ์ 3). */
|
|
412
|
+
mode: { kind: input.mode.kind, ...(input.mode.target ? { target: input.mode.target } : {}) },
|
|
296
413
|
toolAccess: toolAccessFor(context),
|
|
414
|
+
/* The stop notice is a sentence a person reads โ the server writes it in their language (ADR-0054). */
|
|
415
|
+
t,
|
|
297
416
|
hostContext: input.hostContext,
|
|
417
|
+
/*
|
|
418
|
+
* ๊ทผ๊ฑฐ๋ ๋๊ตฌ ๊ฒฐ๊ณผ๋ง์ด ์๋๋ค โ ์์คํ
ํ๋กฌํํธ ยท ์ด ๋ํ์ ๋ง ยท ํ๋ฉด์ด ๋ณด๋ธ ์ค๋
์๋ ๊ทผ๊ฑฐ๋ค
|
|
419
|
+
* (`ai-client-base/utils/grounding`). ์ด ๋ฌธ์ ๊ทธ ์
์ ๋๊ธฐ์ง ์์, **์ฌ๋์ด ๋ฐฉ๊ธ ํ์ดํํ
|
|
420
|
+
* ์๋ณ์**๋ฅผ ๋๋ฐ์ ์ ์ ๋ต์ด ใ๊ทผ๊ฑฐ ์์ใ์ผ๋ก ํ์๋๋ค. ๊ทธ๋ฐ ๊ฒฝ๊ณ ๊ฐ ๋ช ๋ฒ ๋์ค๋ฉด ์ฌ๋์ด ๊ฒฝ๊ณ
|
|
421
|
+
* ์์ฒด๋ฅผ ์ ๋ณด๊ฒ ๋๋ค.
|
|
422
|
+
*/
|
|
423
|
+
groundingSources: groundingSourcesFor({
|
|
424
|
+
systemPrompt: settings.systemPrompt,
|
|
425
|
+
messages: conversation as any,
|
|
426
|
+
hostContext: input.hostContext
|
|
427
|
+
}),
|
|
298
428
|
state: context.state,
|
|
299
|
-
model:
|
|
300
|
-
temperature:
|
|
301
|
-
maxTokens:
|
|
302
|
-
requireToolOnFirstTurn:
|
|
429
|
+
model: settings.model,
|
|
430
|
+
temperature: settings.temperature,
|
|
431
|
+
maxTokens: settings.maxTokens,
|
|
432
|
+
requireToolOnFirstTurn: settings.requireToolOnFirstTurn
|
|
303
433
|
})
|
|
304
434
|
|
|
435
|
+
/*
|
|
436
|
+
* ์ด ๋ต์ด ์ง๋์จ ๋ฌธ โ ADR-0073 ํ์ 1. ํ์๋, ๋ชฉ๋ก ๋ฏธ๋ฆฌ๋ณด๊ธฐ์๋ ๊ฐ์ ๊ฐ์ด ๊ฐ๋ค. ํ์ชฝ๋ง ์ ์ผ๋ฉด
|
|
437
|
+
* ๊ฐ๋ ค์ง ๋ต์ด ๋ชฉ๋ก์๋ ๊ทธ๋๋ก ๋ฌ๋ค.
|
|
438
|
+
*/
|
|
439
|
+
const answerDoors = doorsForAnswer(
|
|
440
|
+
(result.loop.toolUsages ?? []).map((usage: any) => usage?.name).filter(Boolean),
|
|
441
|
+
name => (findToolSpec(name, settings.toolCategories) as any)?.doors
|
|
442
|
+
)
|
|
443
|
+
|
|
305
444
|
let assistantMessageId: string | undefined
|
|
306
445
|
if (session) {
|
|
307
446
|
const stored = await messageRepo.save({
|
|
308
447
|
session: { id: session.id } as any,
|
|
309
448
|
role: 'assistant',
|
|
310
449
|
content: result.reply,
|
|
450
|
+
/*
|
|
451
|
+
* ์ด ๋ต์ด ์ง๋์จ ๋ฌธ์ ํจ๊ป ์ ๋๋ค โ ADR-0073 ํ์ 1. ๋ฐฉ์ ๋๋ฉ์ธ ์์์ ๊ณต๊ฐ์ด๋ฏ๋ก, ์ด ๋ฌธ์ฅ์ด
|
|
452
|
+
* ์ค์ด ๋๋ฅด๋ ๊ฐ์ ๋์ค์ ์ฝ๋ ์ฌ๋์ด ์ด ์ ์๋์ง ํ์ ํ ๊ทผ๊ฑฐ๊ฐ ํ์ ์์ด์ผ ํ๋ค. ์ ์ง ์์ผ๋ฉด
|
|
453
|
+
* ๋น ๋ชฉ๋ก์ด ๋๊ณ , ๋น ๋ชฉ๋ก์ ๋๊ตฌ๋ ์ฝ๋๋ค๋ ๋ป์ด๋ค.
|
|
454
|
+
*/
|
|
455
|
+
doors: answerDoors,
|
|
311
456
|
toolUsages: result.loop.toolUsages?.length ? JSON.stringify(result.loop.toolUsages) : undefined,
|
|
312
457
|
groundingWarnings: result.groundingWarnings?.length ? JSON.stringify(result.groundingWarnings) : undefined,
|
|
313
458
|
creator: user,
|
|
@@ -331,8 +476,25 @@ export class AssistantChatResolver {
|
|
|
331
476
|
await sessionRepo.update(session.id!, {
|
|
332
477
|
aiClientId: client.id,
|
|
333
478
|
updater: user,
|
|
334
|
-
...activityPatch(result.reply)
|
|
479
|
+
...activityPatch(result.reply, new Date(), answerDoors)
|
|
335
480
|
} as any)
|
|
481
|
+
|
|
482
|
+
/*
|
|
483
|
+
* ์ํ ๋ฐ์ผ๋ก ๋ฐ๋ ค๋ ์๋ถ๋ถ์ ์์ฝ์ผ๋ก ์ ๋๋ค โ **๋ต์ ๋๋ ค์ค ๋ค**์ ํ๋ค(์ฌ์ฉ์ ๋๊ธฐ ์๊ฐ์
|
|
484
|
+
* ์์ฝ ํธ์ถ์ ์น์ง ์๋๋ค). ๊ธฐ๋ค๋ฆฌ์ง ์์ผ๋ฏ๋ก ์คํจํด๋ ๋ํ์๋ ์ํฅ์ด ์๋ค: ์์ฝ์ด ์์ผ๋ฉด ๋ค์
|
|
485
|
+
* ํด์ด ใN ๊ฐ ์๋ตใ์ผ๋ก ์ ์งํ๊ฒ ์๋ฆฐ๋ค.
|
|
486
|
+
*
|
|
487
|
+
* ์ด ๋ฌธ์๋ ์ด ํธ์ถ์ด ์์๋ค. ์กฐ๊ฐ์ ๋ค ์์๋๋ฐ(์ํ ยท ์๋ต ์๋ฆผ ยท `lastSummary` ยท ์์ ๊ท์น)
|
|
488
|
+
* ๋ถ๋ฅด๋ ์ชฝ์ด ๋ณด๋ ์ ํ ํ๋๋ฟ์ด์ด์, ์ฑ์ ๋ํ๋ ๋ฉฐ์น ์ด ์ง๋๋ ์๋ถ๋ถ์ ์์ฝํ์ง ์๊ณ ๋งค ํด
|
|
489
|
+
* ใ์๋ตํ๋คใ๊ณ ๋ง ๋งํ๋ค โ ํ์๊ฐ ๊ธธ์ด์ง์๋ก ์ฒ์์ ํฉ์ํ ๊ฒ์ด ๋จผ์ ์ฌ๋ผ์ง๋ค.
|
|
490
|
+
*/
|
|
491
|
+
void foldSessionHistory({
|
|
492
|
+
sessionId: session.id!,
|
|
493
|
+
domainId: domain.id,
|
|
494
|
+
client,
|
|
495
|
+
maxTurns: LLM_HISTORY_MAX_TURNS,
|
|
496
|
+
locale: context.state?.locale
|
|
497
|
+
})
|
|
336
498
|
}
|
|
337
499
|
|
|
338
500
|
return {
|
|
@@ -344,7 +506,8 @@ export class AssistantChatResolver {
|
|
|
344
506
|
toolUsages: result.loop.toolUsages?.length ? result.loop.toolUsages : null,
|
|
345
507
|
offeredTools: result.offeredTools,
|
|
346
508
|
proposals: result.loop.proposals?.length ? result.loop.proposals : null,
|
|
347
|
-
groundingWarnings: result.groundingWarnings?.length ? result.groundingWarnings : null
|
|
509
|
+
groundingWarnings: result.groundingWarnings?.length ? result.groundingWarnings : null,
|
|
510
|
+
historyFolded: historyFolded ?? null
|
|
348
511
|
}
|
|
349
512
|
}
|
|
350
513
|
}
|