@things-factory/ai-assistant 10.1.8 → 10.1.13
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-session-toolbar.ts +70 -0
- package/client/components/board-ai-chat.ts +50 -41
- package/client/index.ts +4 -0
- package/client/utils/assistant-request-context.ts +21 -0
- package/client/utils/assistant-session-controller.ts +166 -0
- package/client/utils/assistant-session-transport.ts +34 -0
- package/dist-client/components/assistant-session-toolbar.d.ts +18 -0
- package/dist-client/components/assistant-session-toolbar.js +109 -0
- package/dist-client/components/assistant-session-toolbar.js.map +1 -0
- package/dist-client/components/board-ai-chat.d.ts +6 -0
- package/dist-client/components/board-ai-chat.js +62 -40
- package/dist-client/components/board-ai-chat.js.map +1 -1
- package/dist-client/index.d.ts +4 -0
- package/dist-client/index.js +4 -0
- package/dist-client/index.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/assistant-request-context.d.ts +10 -0
- package/dist-client/utils/assistant-request-context.js +20 -0
- package/dist-client/utils/assistant-request-context.js.map +1 -0
- package/dist-client/utils/assistant-session-controller.d.ts +49 -0
- package/dist-client/utils/assistant-session-controller.js +165 -0
- package/dist-client/utils/assistant-session-controller.js.map +1 -0
- package/dist-client/utils/assistant-session-transport.d.ts +3 -0
- package/dist-client/utils/assistant-session-transport.js +39 -0
- package/dist-client/utils/assistant-session-transport.js.map +1 -0
- package/dist-server/service/assistant-chat-resolver.js +8 -0
- package/dist-server/service/assistant-chat-resolver.js.map +1 -1
- package/dist-server/service/chat-session/chat-session.js +1 -1
- package/dist-server/service/chat-session/chat-session.js.map +1 -1
- package/dist-server/service/chat-session/session-anchor.d.ts +1 -1
- package/dist-server/service/chat-session/session-anchor.js +3 -1
- package/dist-server/service/chat-session/session-anchor.js.map +1 -1
- package/dist-server/service/chat-session/station-session-guard.d.ts +12 -0
- package/dist-server/service/chat-session/station-session-guard.js +20 -0
- package/dist-server/service/chat-session/station-session-guard.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/docs/session-controller.md +52 -0
- package/package.json +2 -2
- package/server/service/assistant-chat-resolver.ts +9 -0
- package/server/service/chat-session/chat-session.ts +1 -1
- package/server/service/chat-session/session-anchor.ts +3 -1
- package/server/service/chat-session/station-session-guard.ts +17 -0
- package/server/service/session-anchor.test.ts +11 -0
- package/server/service/station-session-guard.test.ts +24 -0
- package/test/assistant-request-context.test.ts +30 -0
- package/test/assistant-session-controller.test.ts +88 -0
- package/test/assistant-session-toolbar.test.ts +29 -0
- package/translations/en.json +6 -0
- package/translations/ko.json +6 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-anchor.js","sourceRoot":"","sources":["../../../server/service/chat-session/session-anchor.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;;;
|
|
1
|
+
{"version":3,"file":"session-anchor.js","sourceRoot":"","sources":["../../../server/service/chat-session/session-anchor.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;;;AAiBH,kDAEC;AAiBD,sCAeC;AASD,kCAIC;AA9DD;;;;;;;;GAQG;AACH,2EAA2E;AAC3E,6EAA6E;AAChE,QAAA,oBAAoB,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAA;AAItH,SAAgB,mBAAmB,CAAC,KAAc;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,4BAA0C,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AACjG,CAAC;AAED,SAAS,gBAAgB,CAAC,UAA8B,EAAE,KAAa;IACrE,MAAM,IAAI,GAAG,UAAU,IAAI,MAAM,CAAA;IACjC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CACb,6BAA6B,KAAK,MAAM,IAAI,2BAA2B;YACrE,YAAY,4BAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI;YAChD,gGAAgG;YAChG,2EAA2E;YAC3E,4EAA4E,CAC/E,CAAA;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,oEAAoE;AACpE,SAAgB,aAAa,CAC3B,UAA8B,EAC9B,QAA4B;IAE5B,MAAM,IAAI,GAAG,gBAAgB,CAAC,UAAU,EAAE,eAAe,CAAC,CAAA;IAC1D,MAAM,EAAE,GAAG,QAAQ,IAAI,SAAS,CAAA;IAChC,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,oDAAoD,IAAI,qBAAqB;YAC3E,+FAA+F;YAC/F,gGAAgG;YAChG,+EAA+E,CAClF,CAAA;IACH,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAA;AACvF,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,WAAW,CAAC,QAAgB,EAAE,UAAkB,EAAE,QAAgB;IAChF,MAAM,IAAI,GAAG,gBAAgB,CAAC,UAAU,EAAE,aAAa,CAAC,CAAA;IACxD,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,CAAA;IAC5E,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,CAAA;AACjE,CAAC","sourcesContent":["/*\n * 세션 앵커 순수 로직 — 리졸버와 분리.\n *\n * chat-session-resolver 는 type-graphql 의 **매개변수 데코레이터**(@Arg)를 쓰므로 jest 의 babel\n * 파서가 읽지 못한다. 순수 함수를 여기 두면 단위 테스트가 가능하다(chat-echo-dedup ·\n * chat-input-builder 와 같은 관례).\n *\n * **앵커란**: 대화가 무엇에 매달려 있는지. 저작 대화는 보드에, 운영 대화는 공간(사이트와 그 공간에서\n * 도는 트윈들)에 매달린다. 앵커가 대화의 성격과 **이력 경계**를 정한다 — 보드 편집 이력에 운영\n * 문답이 끼지 않아야 하고, 그 반대도 마찬가지다.\n *\n * **하위호환**: 기존 행은 앵커 컬럼이 비어 있고 boardId 만 있다. 백필하지 않고 **읽을 때** board\n * 앵커로 간주한다(컬럼 추가만으로 끝내기 위함). 보드 앵커 세션은 anchorId 와 boardId 를 함께\n * 쓴다(dual-write) — 기존 조회·인덱스·소비자(operato-board 등)가 그대로 동작한다.\n *\n * ── Why this file validates instead of casting (2026-09-09) ──────────────────────\n * `anchorColumns` used to do `(anchorType || 'none') as SessionAnchorType`. That is a cast, not a\n * check: the type existed and nothing enforced it, so any string reached a nullable varchar and\n * was stored. The plant lane was about to add a product and stopped to ask which value to write,\n * which is the only reason this was noticed — the column would have accepted whatever it sent.\n *\n * A wrong value here is not a wrong value in one row. The anchor decides which conversations are\n * listed together and which history a reply may see, so a typo silently creates a second,\n * invisible bucket: the session is written, the inbox query for the real anchor does not match\n * it, and the conversation is simply gone from the list with no error anywhere.\n *\n * Two rules follow, and both are refusals rather than defaults:\n *\n * · An unknown anchor type throws. A default of 'none' would be worse than a typo, because it\n * turns \"you named something that does not exist\" into \"this conversation is about nothing\".\n * · Every anchor except 'none' requires an id. An anchor is a pointer; `('space', '')` points\n * nowhere, and `anchorWhere` would then look for an empty string while `anchorColumns` stored\n * NULL — the lookup misses its own row and every call creates another session.\n *\n * 'none' therefore means exactly one thing: **not addressable by anchor.** It is the UI state for\n * \"nothing picked yet\" (twin's dock refuses to create a session while it holds 'none'), not a\n * value a product writes because it has nothing narrower to point at. A product whose subject is\n * the whole tenant anchors to 'domain' and holds the domain id — that conversation is listable and\n * has a history boundary, and \"unanchored\" has neither.\n *\n * Adding a value to this set is an architect's call, not a lane's. A new anchor type means a new\n * kind of subject a conversation can be about, and it is stored — once rows carry it, renaming it\n * orphans them.\n */\n\n/**\n * 대화가 매달릴 수 있는 것. **저장되는 값이므로 늘리는 것은 아키텍트 판단이다.**\n *\n * board 보드 저작 대화 — 그 보드의 편집 이력\n * space 공간 운영 대화 — 사이트와 그 공간에서 도는 트윈들\n * instance 트윈 하나에 대한 대화\n * domain 테넌트 전체가 주제인 대화 — 공장 전체 문답처럼 더 좁게 가리킬 것이 없을 때\n * none 앵커로 찾을 수 없는 대화. 「아직 아무것도 고르지 않았다」는 화면 상태다\n */\n// Figure-specific history requested explicitly: reuse the existing varchar\n// anchor columns, without writing Figure ids into the legacy boardId column.\nexport const SESSION_ANCHOR_TYPES = ['board', 'figure', 'application', 'space', 'instance', 'domain', 'none'] as const\n\nexport type SessionAnchorType = (typeof SESSION_ANCHOR_TYPES)[number]\n\nexport function isSessionAnchorType(value: unknown): value is SessionAnchorType {\n return typeof value === 'string' && (SESSION_ANCHOR_TYPES as readonly string[]).includes(value)\n}\n\nfunction assertAnchorType(anchorType: string | undefined, where: string): SessionAnchorType {\n const type = anchorType || 'none'\n if (!isSessionAnchorType(type)) {\n throw new Error(\n `[board-ai/session-anchor] ${where}: '${type}' is not an anchor type. ` +\n `Allowed: ${SESSION_ANCHOR_TYPES.join(' · ')}. ` +\n 'The anchor decides which conversations are listed together and which history a reply may see, ' +\n 'so an unrecognised value would write a session the inbox can never find. ' +\n 'If a new kind of subject is needed, raise it — this set is stored in rows.'\n )\n }\n return type\n}\n\n/** 앵커 → 저장할 컬럼 값. 보드 앵커는 boardId 에도 같은 값을 넣어 기존 조회가 그대로 동작하게 한다. */\nexport function anchorColumns(\n anchorType: string | undefined,\n anchorId: string | undefined\n): { anchorType: SessionAnchorType; anchorId?: string; boardId?: string } {\n const type = assertAnchorType(anchorType, 'anchorColumns')\n const id = anchorId || undefined\n if (type !== 'none' && !id) {\n throw new Error(\n `[board-ai/session-anchor] anchorColumns: anchor '${type}' was given no id. ` +\n 'An anchor is a pointer, so this would store NULL while the lookup searches for the id it was ' +\n 'given — the session would not be found by its own anchor and every call would create another. ' +\n \"Use 'none' for a conversation that is deliberately not addressable by anchor.\"\n )\n }\n return { anchorType: type, anchorId: id, boardId: type === 'board' ? id : undefined }\n}\n\n/**\n * 앵커 조회 조건. 보드 앵커는 **앵커 컬럼이 빈 옛 행도 잡아야** 하므로 boardId 로 찾는다\n * (dual-write 덕분에 신규 행도 같은 조건에 걸린다). 그 밖 앵커는 앵커 컬럼으로 찾는다.\n *\n * 여기서도 앵커 이름을 검사한다 — 읽기 경로에서 틀린 이름은 오류가 아니라 **0 행**으로 나오고,\n * 화면은 「대화가 없다」로 그린다. 그 증상으로는 원인을 못 찾는다.\n */\nexport function anchorWhere(domainId: string, anchorType: string, anchorId: string): any {\n const type = assertAnchorType(anchorType, 'anchorWhere')\n if (type === 'board') return { boardId: anchorId, domain: { id: domainId } }\n return { anchorType: type, anchorId, domain: { id: domainId } }\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Station authoring must never append its design snapshot to another subject's history.
|
|
2
|
+
* This is a consistency check, not authorization; tenant/privilege checks stay in the resolver.
|
|
3
|
+
* Older Board sessions have only boardId, so preserve that supported read shape.
|
|
4
|
+
*/
|
|
5
|
+
export declare function assertStationSession(session: {
|
|
6
|
+
anchorType?: string | null;
|
|
7
|
+
anchorId?: string | null;
|
|
8
|
+
boardId?: string | null;
|
|
9
|
+
} | undefined, host: {
|
|
10
|
+
surface?: string;
|
|
11
|
+
boardId?: unknown;
|
|
12
|
+
} | undefined): void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertStationSession = assertStationSession;
|
|
4
|
+
/** Station authoring must never append its design snapshot to another subject's history.
|
|
5
|
+
* This is a consistency check, not authorization; tenant/privilege checks stay in the resolver.
|
|
6
|
+
* Older Board sessions have only boardId, so preserve that supported read shape.
|
|
7
|
+
*/
|
|
8
|
+
function assertStationSession(session, host) {
|
|
9
|
+
if (host?.surface !== 'station-screen-modeller')
|
|
10
|
+
return;
|
|
11
|
+
const boardId = host.boardId;
|
|
12
|
+
const legacy = Boolean(session && !session.anchorType && session.boardId);
|
|
13
|
+
const valid = typeof boardId === 'string' && boardId.length > 0 && session &&
|
|
14
|
+
(session.anchorType === 'board' || legacy) &&
|
|
15
|
+
(session.anchorId || session.boardId) === boardId &&
|
|
16
|
+
(!session.boardId || session.boardId === boardId);
|
|
17
|
+
if (!valid)
|
|
18
|
+
throw new Error('현재 Station Screen과 대화 이력이 일치하지 않습니다. 해당 화면의 대화를 다시 여세요.');
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=station-session-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"station-session-guard.js","sourceRoot":"","sources":["../../../server/service/chat-session/station-session-guard.ts"],"names":[],"mappings":";;AAIA,oDAYC;AAhBD;;;GAGG;AACH,SAAgB,oBAAoB,CAClC,OAAuF,EACvF,IAAkD;IAElD,IAAG,IAAI,EAAE,OAAO,KAAG,yBAAyB;QAAC,OAAM;IACnD,MAAM,OAAO,GAAC,IAAI,CAAC,OAAO,CAAA;IAC1B,MAAM,MAAM,GAAC,OAAO,CAAC,OAAO,IAAE,CAAC,OAAO,CAAC,UAAU,IAAE,OAAO,CAAC,OAAO,CAAC,CAAA;IACnE,MAAM,KAAK,GAAC,OAAO,OAAO,KAAG,QAAQ,IAAE,OAAO,CAAC,MAAM,GAAC,CAAC,IAAE,OAAO;QAC9D,CAAC,OAAO,CAAC,UAAU,KAAG,OAAO,IAAE,MAAM,CAAC;QACtC,CAAC,OAAO,CAAC,QAAQ,IAAE,OAAO,CAAC,OAAO,CAAC,KAAG,OAAO;QAC7C,CAAC,CAAC,OAAO,CAAC,OAAO,IAAE,OAAO,CAAC,OAAO,KAAG,OAAO,CAAC,CAAA;IAC/C,IAAG,CAAC,KAAK;QAAC,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAA;AACtF,CAAC","sourcesContent":["/** Station authoring must never append its design snapshot to another subject's history.\n * This is a consistency check, not authorization; tenant/privilege checks stay in the resolver.\n * Older Board sessions have only boardId, so preserve that supported read shape.\n */\nexport function assertStationSession(\n session: {anchorType?:string|null;anchorId?:string|null;boardId?:string|null}|undefined,\n host: {surface?:string;boardId?:unknown}|undefined\n) {\n if(host?.surface!=='station-screen-modeller')return\n const boardId=host.boardId\n const legacy=Boolean(session&&!session.anchorType&&session.boardId)\n const valid=typeof boardId==='string'&&boardId.length>0&&session&&\n (session.anchorType==='board'||legacy)&&\n (session.anchorId||session.boardId)===boardId&&\n (!session.boardId||session.boardId===boardId)\n if(!valid)throw new Error('현재 Station Screen과 대화 이력이 일치하지 않습니다. 해당 화면의 대화를 다시 여세요.')\n}\n"]}
|