@things-factory/ai-assistant 10.1.156 → 10.1.159

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.
Files changed (75) hide show
  1. package/client/components/assistant-chat.ts +24 -213
  2. package/client/components/assistant-dock.ts +11 -32
  3. package/client/components/assistant-mode.test.ts +1 -43
  4. package/client/components/assistant-mode.ts +0 -47
  5. package/client/utils/assistant-surface.ts +1 -1
  6. package/client/utils/composer-frame.ts +6 -4
  7. package/dist-client/components/assistant-chat.d.ts +5 -69
  8. package/dist-client/components/assistant-chat.js +24 -205
  9. package/dist-client/components/assistant-chat.js.map +1 -1
  10. package/dist-client/components/assistant-dock.d.ts +0 -3
  11. package/dist-client/components/assistant-dock.js +10 -34
  12. package/dist-client/components/assistant-dock.js.map +1 -1
  13. package/dist-client/components/assistant-mode.d.ts +0 -33
  14. package/dist-client/components/assistant-mode.js +0 -37
  15. package/dist-client/components/assistant-mode.js.map +1 -1
  16. package/dist-client/components/assistant-mode.test.js +1 -35
  17. package/dist-client/components/assistant-mode.test.js.map +1 -1
  18. package/dist-client/tsconfig.tsbuildinfo +1 -1
  19. package/dist-client/utils/assistant-surface.d.ts +1 -1
  20. package/dist-client/utils/assistant-surface.js.map +1 -1
  21. package/dist-client/utils/composer-frame.d.ts +5 -3
  22. package/dist-client/utils/composer-frame.js +4 -4
  23. package/dist-client/utils/composer-frame.js.map +1 -1
  24. package/dist-server/index.d.ts +1 -0
  25. package/dist-server/index.js +2 -0
  26. package/dist-server/index.js.map +1 -1
  27. package/dist-server/service/assistant-chat-resolver.d.ts +1 -0
  28. package/dist-server/service/assistant-chat-resolver.js +30 -6
  29. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  30. package/dist-server/service/assistant-mode/assistant-mode-resolver.d.ts +1 -2
  31. package/dist-server/service/assistant-mode/assistant-mode-resolver.js +13 -24
  32. package/dist-server/service/assistant-mode/assistant-mode-resolver.js.map +1 -1
  33. package/dist-server/service/assistant-mode/fallback-subjects.d.ts +1 -0
  34. package/dist-server/service/assistant-mode/fallback-subjects.js +37 -0
  35. package/dist-server/service/assistant-mode/fallback-subjects.js.map +1 -0
  36. package/dist-server/service/assistant-mode/index.d.ts +2 -1
  37. package/dist-server/service/assistant-mode/index.js +7 -2
  38. package/dist-server/service/assistant-mode/index.js.map +1 -1
  39. package/dist-server/service/assistant-mode/registry.d.ts +2 -6
  40. package/dist-server/service/assistant-mode/registry.js +16 -56
  41. package/dist-server/service/assistant-mode/registry.js.map +1 -1
  42. package/dist-server/service/assistant-mode/resolve.js +3 -1
  43. package/dist-server/service/assistant-mode/resolve.js.map +1 -1
  44. package/dist-server/service/assistant-mode/screen-rules.d.ts +25 -0
  45. package/dist-server/service/assistant-mode/screen-rules.js +47 -0
  46. package/dist-server/service/assistant-mode/screen-rules.js.map +1 -0
  47. package/dist-server/service/assistant-mode/types.d.ts +8 -25
  48. package/dist-server/service/assistant-mode/types.js.map +1 -1
  49. package/dist-server/service/testing/mode-conformance.d.ts +11 -0
  50. package/dist-server/service/testing/mode-conformance.js +28 -0
  51. package/dist-server/service/testing/mode-conformance.js.map +1 -1
  52. package/dist-server/tool-registry-boot-report.d.ts +1 -1
  53. package/dist-server/tool-registry-boot-report.js +16 -7
  54. package/dist-server/tool-registry-boot-report.js.map +1 -1
  55. package/dist-server/tsconfig.tsbuildinfo +1 -1
  56. package/package.json +8 -8
  57. package/server/index.ts +2 -0
  58. package/server/service/assistant-chat-resolver.ts +33 -7
  59. package/server/service/assistant-mode/assistant-mode-resolver.ts +12 -25
  60. package/server/service/assistant-mode/fallback-subjects.ts +36 -0
  61. package/server/service/assistant-mode/index.ts +2 -1
  62. package/server/service/assistant-mode/one-conversation-per-subject.test.ts +37 -0
  63. package/server/service/assistant-mode/open-subject-door.test.ts +29 -0
  64. package/server/service/assistant-mode/registry.ts +14 -55
  65. package/server/service/assistant-mode/resolve.test.ts +10 -1
  66. package/server/service/assistant-mode/resolve.ts +3 -1
  67. package/server/service/assistant-mode/screen-rules.test.ts +39 -0
  68. package/server/service/assistant-mode/screen-rules.ts +47 -0
  69. package/server/service/assistant-mode/types.ts +8 -31
  70. package/server/service/testing/mode-conformance.ts +27 -1
  71. package/server/tool-registry-boot-report.test.ts +14 -1
  72. package/server/tool-registry-boot-report.ts +16 -7
  73. package/test/assistant-dock.test.ts +27 -4
  74. package/test/composer-frame.test.ts +12 -2
  75. package/server/service/assistant-mode/subject-defaults.test.ts +0 -86
@@ -46,7 +46,7 @@ export interface BoardSurface {
46
46
  export interface AssistantSurface {
47
47
  /** Identity - a different key is a different conversation. */
48
48
  key: string;
49
- /** Matched against the server's mode `subjects` (assistantModes(subject)). */
49
+ /** Which conversation opens here: the one mode whose `subjects` name it (assistantModeFor(subject)). */
50
50
  subject: string;
51
51
  /** The session anchor, or why none could stand. May ask the server (a row uuid, a domain id). */
52
52
  anchor(): Promise<AnchorResult> | AnchorResult;
@@ -1 +1 @@
1
- {"version":3,"file":"assistant-surface.js","sourceRoot":"","sources":["../../client/utils/assistant-surface.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * What the screen shows - the one way a screen tells the AI dock (design/ai/shared-dock.md §3).\n *\n * Before this there were five ways: `context.subject` (warehouse), `BoardEditHost` (twin), `StationAITarget`\n * (plant), window events (figure) and the board modeller's own panel. The dock now reads one answer:\n *\n * 1. an editor that holds a draft provides a surface provideAssistantSurface(owner, surface)\n * 2. a page that shows one thing says so in its context get context() { return { subject: { type, id } } }\n * and the app says once how such a subject becomes a surface registerSubjectSurface(type, make)\n * 3. otherwise the app's home registerAssistantHome(surface)\n *\n * The first that answers wins. The dock follows changes through `subscribeAssistantSurface`.\n */\nimport { store } from '@operato/shell'\n\nimport type { ProposalOutcome } from './assistant-proposals.js'\n\n/** The row a conversation hangs on - its session anchor (ADR-0072 decision 6). */\nexport interface AssistantAnchor {\n anchorType: string\n anchorId: string\n}\n\n/** No anchor could stand - said to the person as a fact and what to do. */\nexport interface AssistantStuck {\n stuck: string\n message: string\n}\n\nexport type AnchorResult = AssistantAnchor | AssistantStuck\n\n/** A route the person can take from a surface when the next useful action lives in the app. */\nexport interface AssistantSurfaceNextStep {\n /** The small, concrete action shown below the dock header. */\n label(): string\n /** A route inside the current application. */\n href: string\n /** Why this is the useful next action, in the person's words. */\n description?(): string\n}\n\nexport function isAnchor(result: AnchorResult | undefined): result is AssistantAnchor {\n return !!result && 'anchorId' in result && !!result.anchorId\n}\n\n/** For a surface that authors a board - the chat gets the board, the dock applies its patches. */\nexport interface BoardSurface {\n currentBoard?: () => any\n boardProvider?: () => any\n selectedRefids?: () => number[]\n knownTypes?: () => string[]\n categories?: () => any[]\n componentSchemas?: () => any\n searchProvider?: (query: string) => any[]\n applyPatch(detail: any): Promise<{ applied: number; missed: number; note?: string }> | { applied: number; missed: number; note?: string }\n revertPatch?(patchId: string): Promise<void> | void\n applyAction?(detail: any): Promise<void> | void\n /** The chat door for board authoring, until board-ai joins the neutral door (ADR-0061 decision 0-1). */\n chatEndpoint?: 'boardAIChat' | 'assistantChat'\n}\n\nexport interface AssistantSurface {\n /** Identity - a different key is a different conversation. */\n key: string\n /** Matched against the server's mode `subjects` (assistantModes(subject)). */\n subject: string\n /** The session anchor, or why none could stand. May ask the server (a row uuid, a domain id). */\n anchor(): Promise<AnchorResult> | AnchorResult\n /** Words for the header - never a raw identifier. */\n label(): string\n detail?(): string\n icon?: string\n /** An app-owned route that helps the person make this surface useful (for example, create the first Twin). */\n nextStep?: AssistantSurfaceNextStep\n /** Read at the moment of sending - never cached, so a draft cannot go stale. */\n hostContext?(): Record<string, unknown>\n board?: BoardSurface\n /** The dock opened another conversation for this surface - e.g. the board modeller records its direct edits under it. */\n sessionChanged?(sessionId: string): void\n /**\n * What became of a proposal whose outcome never reached the conversation (ADR-0097 Q-D4 ④) - asked by\n * \"check result\", never to run it again. Answer from the host's own record, keyed by the proposal's key.\n * `undefined` when the host cannot tell (not saved, undone, never applied): the card stays \"no answer\n * received\" - an unknown result is not a refusal.\n */\n proposalOutcome?(proposalKey: string, proposal: any): Promise<ProposalOutcome | undefined> | ProposalOutcome | undefined\n}\n\ntype Listener = () => void\n\nconst provided = new Map<object, AssistantSurface>()\nlet lastOwner: object | undefined\nconst subjectMakers = new Map<string, (subject: { type: string; id?: string }) => AssistantSurface>()\nlet home: AssistantSurface | undefined\nconst listeners = new Set<Listener>()\n\nfunction changed() {\n listeners.forEach(listener => {\n try {\n listener()\n } catch {\n /* one listener's error does not stop the others */\n }\n })\n}\n\n/** An editor with a draft says it is on screen. The last one provided wins until it is cleared. */\nexport function provideAssistantSurface(owner: object, surface: AssistantSurface): void {\n provided.set(owner, surface)\n lastOwner = owner\n changed()\n}\n\n/** The editor is gone or inactive. A later provider that replaced it is left alone. */\nexport function clearAssistantSurface(owner: object): void {\n if (!provided.delete(owner)) return\n if (lastOwner === owner) lastOwner = [...provided.keys()].pop()\n changed()\n}\n\n/** The editor's draft or identity changed - the dock reads it again. */\nexport function notifyAssistantSurface(owner: object): void {\n if (provided.has(owner)) changed()\n}\n\n/** How a page's `context.subject` of this type becomes a surface - declared once by the app. */\nexport function registerSubjectSurface(type: string, make: (subject: { type: string; id?: string }) => AssistantSurface): void {\n subjectMakers.set(type, make)\n changed()\n}\n\n/** The app's own conversation when the screen shows nothing in particular - a factory, a site, a catalog. */\nexport function registerAssistantHome(surface: AssistantSurface): void {\n home = surface\n changed()\n}\n\nexport function subscribeAssistantSurface(listener: Listener): () => void {\n listeners.add(listener)\n return () => listeners.delete(listener)\n}\n\nlet pageSubjectKey = ''\nlet pageSurface: AssistantSurface | undefined\n\nfunction surfaceFromPage(): AssistantSurface | undefined {\n const subject = (store.getState() as any)?.route?.context?.subject\n const type = subject?.type\n const make = type ? subjectMakers.get(type) : undefined\n if (!make) return undefined\n const key = JSON.stringify([type, subject?.id ?? ''])\n if (key !== pageSubjectKey) {\n pageSubjectKey = key\n pageSurface = make({ type, id: subject?.id })\n }\n return pageSurface\n}\n\n/** What the dock talks about now: a provided editor, else the page's subject, else the app's home. */\nexport function currentAssistantSurface(): AssistantSurface | undefined {\n return (lastOwner && provided.get(lastOwner)) || surfaceFromPage() || home\n}\n\n/** The app's home surface - for the header's \"back to the app's conversation\". */\nexport function assistantHomeSurface(): AssistantSurface | undefined {\n return home\n}\n\n/* The page context lives in the store; a page change is a surface change. */\nlet lastRouteSubject = ''\nstore.subscribe(() => {\n const subject = (store.getState() as any)?.route?.context?.subject\n const key = JSON.stringify([subject?.type ?? '', subject?.id ?? ''])\n if (key !== lastRouteSubject) {\n lastRouteSubject = key\n changed()\n }\n})\n"]}
1
+ {"version":3,"file":"assistant-surface.js","sourceRoot":"","sources":["../../client/utils/assistant-surface.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * What the screen shows - the one way a screen tells the AI dock (design/ai/shared-dock.md §3).\n *\n * Before this there were five ways: `context.subject` (warehouse), `BoardEditHost` (twin), `StationAITarget`\n * (plant), window events (figure) and the board modeller's own panel. The dock now reads one answer:\n *\n * 1. an editor that holds a draft provides a surface provideAssistantSurface(owner, surface)\n * 2. a page that shows one thing says so in its context get context() { return { subject: { type, id } } }\n * and the app says once how such a subject becomes a surface registerSubjectSurface(type, make)\n * 3. otherwise the app's home registerAssistantHome(surface)\n *\n * The first that answers wins. The dock follows changes through `subscribeAssistantSurface`.\n */\nimport { store } from '@operato/shell'\n\nimport type { ProposalOutcome } from './assistant-proposals.js'\n\n/** The row a conversation hangs on - its session anchor (ADR-0072 decision 6). */\nexport interface AssistantAnchor {\n anchorType: string\n anchorId: string\n}\n\n/** No anchor could stand - said to the person as a fact and what to do. */\nexport interface AssistantStuck {\n stuck: string\n message: string\n}\n\nexport type AnchorResult = AssistantAnchor | AssistantStuck\n\n/** A route the person can take from a surface when the next useful action lives in the app. */\nexport interface AssistantSurfaceNextStep {\n /** The small, concrete action shown below the dock header. */\n label(): string\n /** A route inside the current application. */\n href: string\n /** Why this is the useful next action, in the person's words. */\n description?(): string\n}\n\nexport function isAnchor(result: AnchorResult | undefined): result is AssistantAnchor {\n return !!result && 'anchorId' in result && !!result.anchorId\n}\n\n/** For a surface that authors a board - the chat gets the board, the dock applies its patches. */\nexport interface BoardSurface {\n currentBoard?: () => any\n boardProvider?: () => any\n selectedRefids?: () => number[]\n knownTypes?: () => string[]\n categories?: () => any[]\n componentSchemas?: () => any\n searchProvider?: (query: string) => any[]\n applyPatch(detail: any): Promise<{ applied: number; missed: number; note?: string }> | { applied: number; missed: number; note?: string }\n revertPatch?(patchId: string): Promise<void> | void\n applyAction?(detail: any): Promise<void> | void\n /** The chat door for board authoring, until board-ai joins the neutral door (ADR-0061 decision 0-1). */\n chatEndpoint?: 'boardAIChat' | 'assistantChat'\n}\n\nexport interface AssistantSurface {\n /** Identity - a different key is a different conversation. */\n key: string\n /** Which conversation opens here: the one mode whose `subjects` name it (assistantModeFor(subject)). */\n subject: string\n /** The session anchor, or why none could stand. May ask the server (a row uuid, a domain id). */\n anchor(): Promise<AnchorResult> | AnchorResult\n /** Words for the header - never a raw identifier. */\n label(): string\n detail?(): string\n icon?: string\n /** An app-owned route that helps the person make this surface useful (for example, create the first Twin). */\n nextStep?: AssistantSurfaceNextStep\n /** Read at the moment of sending - never cached, so a draft cannot go stale. */\n hostContext?(): Record<string, unknown>\n board?: BoardSurface\n /** The dock opened another conversation for this surface - e.g. the board modeller records its direct edits under it. */\n sessionChanged?(sessionId: string): void\n /**\n * What became of a proposal whose outcome never reached the conversation (ADR-0097 Q-D4 ④) - asked by\n * \"check result\", never to run it again. Answer from the host's own record, keyed by the proposal's key.\n * `undefined` when the host cannot tell (not saved, undone, never applied): the card stays \"no answer\n * received\" - an unknown result is not a refusal.\n */\n proposalOutcome?(proposalKey: string, proposal: any): Promise<ProposalOutcome | undefined> | ProposalOutcome | undefined\n}\n\ntype Listener = () => void\n\nconst provided = new Map<object, AssistantSurface>()\nlet lastOwner: object | undefined\nconst subjectMakers = new Map<string, (subject: { type: string; id?: string }) => AssistantSurface>()\nlet home: AssistantSurface | undefined\nconst listeners = new Set<Listener>()\n\nfunction changed() {\n listeners.forEach(listener => {\n try {\n listener()\n } catch {\n /* one listener's error does not stop the others */\n }\n })\n}\n\n/** An editor with a draft says it is on screen. The last one provided wins until it is cleared. */\nexport function provideAssistantSurface(owner: object, surface: AssistantSurface): void {\n provided.set(owner, surface)\n lastOwner = owner\n changed()\n}\n\n/** The editor is gone or inactive. A later provider that replaced it is left alone. */\nexport function clearAssistantSurface(owner: object): void {\n if (!provided.delete(owner)) return\n if (lastOwner === owner) lastOwner = [...provided.keys()].pop()\n changed()\n}\n\n/** The editor's draft or identity changed - the dock reads it again. */\nexport function notifyAssistantSurface(owner: object): void {\n if (provided.has(owner)) changed()\n}\n\n/** How a page's `context.subject` of this type becomes a surface - declared once by the app. */\nexport function registerSubjectSurface(type: string, make: (subject: { type: string; id?: string }) => AssistantSurface): void {\n subjectMakers.set(type, make)\n changed()\n}\n\n/** The app's own conversation when the screen shows nothing in particular - a factory, a site, a catalog. */\nexport function registerAssistantHome(surface: AssistantSurface): void {\n home = surface\n changed()\n}\n\nexport function subscribeAssistantSurface(listener: Listener): () => void {\n listeners.add(listener)\n return () => listeners.delete(listener)\n}\n\nlet pageSubjectKey = ''\nlet pageSurface: AssistantSurface | undefined\n\nfunction surfaceFromPage(): AssistantSurface | undefined {\n const subject = (store.getState() as any)?.route?.context?.subject\n const type = subject?.type\n const make = type ? subjectMakers.get(type) : undefined\n if (!make) return undefined\n const key = JSON.stringify([type, subject?.id ?? ''])\n if (key !== pageSubjectKey) {\n pageSubjectKey = key\n pageSurface = make({ type, id: subject?.id })\n }\n return pageSurface\n}\n\n/** What the dock talks about now: a provided editor, else the page's subject, else the app's home. */\nexport function currentAssistantSurface(): AssistantSurface | undefined {\n return (lastOwner && provided.get(lastOwner)) || surfaceFromPage() || home\n}\n\n/** The app's home surface - for the header's \"back to the app's conversation\". */\nexport function assistantHomeSurface(): AssistantSurface | undefined {\n return home\n}\n\n/* The page context lives in the store; a page change is a surface change. */\nlet lastRouteSubject = ''\nstore.subscribe(() => {\n const subject = (store.getState() as any)?.route?.context?.subject\n const key = JSON.stringify([subject?.type ?? '', subject?.id ?? ''])\n if (key !== lastRouteSubject) {\n lastRouteSubject = key\n changed()\n }\n})\n"]}
@@ -34,9 +34,9 @@ export declare function tableAsWords(table: {
34
34
  /** The words appended to what is already written - on a line of their own. */
35
35
  export declare function appendWords(written: string, words: string): string;
36
36
  /**
37
- * The request the empty box suggests, which a Tab writes in: the conversation's first example request, its own
38
- * before the host's. A placeholder is how to write, not a request, so it is never the suggestion. Once the
39
- * conversation is under way an opening example no longer fits, and nothing is suggested.
37
+ * The request the empty box suggests, which a Tab writes in (design/ai/screen-pattern.md §4-1). Before anything is
38
+ * said it is the conversation's first example request, its own before the host's; after an answer it is the next
39
+ * request that answer named. A placeholder is how to write, not a request, so it is never the suggestion.
40
40
  */
41
41
  export declare function composerSuggestion(input: {
42
42
  started: boolean;
@@ -46,4 +46,6 @@ export declare function composerSuggestion(input: {
46
46
  hostExamples?: Array<{
47
47
  items: string[];
48
48
  }>;
49
+ /** The next request the last answer named. */
50
+ answered?: string;
49
51
  }): string | undefined;
@@ -51,13 +51,13 @@ export function appendWords(written, words) {
51
51
  return written.trim() ? `${written.replace(/\s+$/, '')}\n${words}` : words;
52
52
  }
53
53
  /**
54
- * The request the empty box suggests, which a Tab writes in: the conversation's first example request, its own
55
- * before the host's. A placeholder is how to write, not a request, so it is never the suggestion. Once the
56
- * conversation is under way an opening example no longer fits, and nothing is suggested.
54
+ * The request the empty box suggests, which a Tab writes in (design/ai/screen-pattern.md §4-1). Before anything is
55
+ * said it is the conversation's first example request, its own before the host's; after an answer it is the next
56
+ * request that answer named. A placeholder is how to write, not a request, so it is never the suggestion.
57
57
  */
58
58
  export function composerSuggestion(input) {
59
59
  if (input.started)
60
- return undefined;
60
+ return input.answered?.trim() || undefined;
61
61
  const groups = input.modeExamples?.length ? input.modeExamples : input.hostExamples;
62
62
  return groups?.find(group => group.items.length > 0)?.items[0];
63
63
  }
@@ -1 +1 @@
1
- {"version":3,"file":"composer-frame.js","sourceRoot":"","sources":["../../client/utils/composer-frame.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,8EAA8E;AAC9E,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;AAElC,gEAAgE;AAChE,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,IAAY,EAAE,OAAe;IAC3E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,eAAe,CAAC,CAAC,CAAA;IACjE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,EAAE,CAAA;AACtF,CAAC;AAID;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,CAAc,EACd,IAAa,EACb,MAAgD,EAAE;IAElD,IAAI,CAAC,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC;QACnH,OAAO,iBAAiB,CAAA;IAC1B,CAAC;IACD,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAA;IACrD,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,CAAC,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IAC9C,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO;QAAE,OAAO,MAAM,CAAA;IACzC,IAAI,IAAI,IAAI,CAAC,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAA;IACnC,OAAO,MAAM,CAAA;AACf,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,YAAY,CAAC,QAA4B;IACvD,OAAO,kBAAkB,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAA;AAC3E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAiD;IAC5E,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAC3E,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC9E,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,KAAa;IACxD,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;AAC5E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAIlC;IACC,IAAI,KAAK,CAAC,OAAO;QAAE,OAAO,SAAS,CAAA;IACnC,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAA;IACnF,OAAO,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;AAChE,CAAC","sourcesContent":["/*\n * The composer's frame (operato-application/design/ai/shared-dock.md §10) - how tall it stands, what Enter does,\n * and what a pasted table gives back as words. Pure: the chat hands in its measures and the key.\n *\n * - The box grows with its text up to 40% of the chat's height, then scrolls. It never stands under its resting\n * height, and on a chat too short for 40% to hold that height the resting height wins.\n * - The wide box (넓게 쓰기) covers the conversation inside the dock; the page beside it stays where it was.\n * - In the small box Enter sends and Shift+Enter breaks the line, as in every chat. In the wide box Enter breaks\n * the line - long writing is made of lines - and Ctrl/Cmd+Enter sends. Ctrl/Cmd+Enter sends in both.\n */\n\n/** The part of the chat's height the small box may take before it scrolls. */\nexport const COMPOSER_GROWTH = 0.4\n\n/** The box's height for its content, both border-box pixels. */\nexport function composerHeight(content: number, chat: number, resting: number): { height: number; scrolls: boolean } {\n const cap = Math.max(resting, Math.floor(chat * COMPOSER_GROWTH))\n return { height: Math.min(Math.max(content, resting), cap), scrolls: content > cap }\n}\n\nexport type ComposerKey = { key: string; shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean; altKey?: boolean }\n\n/**\n * What a key does in the box: send, close the wide box, take the suggestion, or nothing of ours (the textarea keeps\n * it). The words shown in an empty box are a request the screen suggests - a plain Tab writes them in, so the person\n * does not have to type what the screen already said.\n */\nexport function composerKeyAction(\n e: ComposerKey,\n wide: boolean,\n box: { empty?: boolean; suggestion?: string } = {}\n): 'send' | 'narrow' | 'take-suggestion' | null {\n if (e.key === 'Tab' && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey && box.empty && box.suggestion?.trim()) {\n return 'take-suggestion'\n }\n if (e.key === 'Escape') return wide ? 'narrow' : null\n if (e.key !== 'Enter' || e.altKey) return null\n if (e.ctrlKey || e.metaKey) return 'send'\n if (wide || e.shiftKey) return null\n return 'send'\n}\n\n/** The shortcut the wide box's send button names, in the keys of this machine. */\nexport function sendShortcut(platform: string | undefined): string {\n return /mac|iphone|ipad/i.test(platform ?? '') ? '⌘ Enter' : 'Ctrl+Enter'\n}\n\n/**\n * A pasted table given back as words: what the clipboard held as text, or - when it held only HTML - the rows with\n * a tab between cells, as a spreadsheet copies them.\n */\nexport function tableAsWords(table: { rows: string[][]; text?: string | null }): string {\n const text = (table.text ?? '').replace(/\\r\\n?/g, '\\n').replace(/\\n+$/, '')\n return text.trim() ? text : table.rows.map(row => row.join('\\t')).join('\\n')\n}\n\n/** The words appended to what is already written - on a line of their own. */\nexport function appendWords(written: string, words: string): string {\n return written.trim() ? `${written.replace(/\\s+$/, '')}\\n${words}` : words\n}\n\n/**\n * The request the empty box suggests, which a Tab writes in: the conversation's first example request, its own\n * before the host's. A placeholder is how to write, not a request, so it is never the suggestion. Once the\n * conversation is under way an opening example no longer fits, and nothing is suggested.\n */\nexport function composerSuggestion(input: {\n started: boolean\n modeExamples?: Array<{ items: string[] }>\n hostExamples?: Array<{ items: string[] }>\n}): string | undefined {\n if (input.started) return undefined\n const groups = input.modeExamples?.length ? input.modeExamples : input.hostExamples\n return groups?.find(group => group.items.length > 0)?.items[0]\n}\n"]}
1
+ {"version":3,"file":"composer-frame.js","sourceRoot":"","sources":["../../client/utils/composer-frame.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The composer's frame (operato-application/design/ai/shared-dock.md §10) - how tall it stands, what Enter does,\n * and what a pasted table gives back as words. Pure: the chat hands in its measures and the key.\n *\n * - The box grows with its text up to 40% of the chat's height, then scrolls. It never stands under its resting\n * height, and on a chat too short for 40% to hold that height the resting height wins.\n * - The wide box (넓게 쓰기) covers the conversation inside the dock; the page beside it stays where it was.\n * - In the small box Enter sends and Shift+Enter breaks the line, as in every chat. In the wide box Enter breaks\n * the line - long writing is made of lines - and Ctrl/Cmd+Enter sends. Ctrl/Cmd+Enter sends in both.\n */\n\n/** The part of the chat's height the small box may take before it scrolls. */\nexport const COMPOSER_GROWTH = 0.4\n\n/** The box's height for its content, both border-box pixels. */\nexport function composerHeight(content: number, chat: number, resting: number): { height: number; scrolls: boolean } {\n const cap = Math.max(resting, Math.floor(chat * COMPOSER_GROWTH))\n return { height: Math.min(Math.max(content, resting), cap), scrolls: content > cap }\n}\n\nexport type ComposerKey = { key: string; shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean; altKey?: boolean }\n\n/**\n * What a key does in the box: send, close the wide box, take the suggestion, or nothing of ours (the textarea keeps\n * it). The words shown in an empty box are a request the screen suggests - a plain Tab writes them in, so the person\n * does not have to type what the screen already said.\n */\nexport function composerKeyAction(\n e: ComposerKey,\n wide: boolean,\n box: { empty?: boolean; suggestion?: string } = {}\n): 'send' | 'narrow' | 'take-suggestion' | null {\n if (e.key === 'Tab' && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey && box.empty && box.suggestion?.trim()) {\n return 'take-suggestion'\n }\n if (e.key === 'Escape') return wide ? 'narrow' : null\n if (e.key !== 'Enter' || e.altKey) return null\n if (e.ctrlKey || e.metaKey) return 'send'\n if (wide || e.shiftKey) return null\n return 'send'\n}\n\n/** The shortcut the wide box's send button names, in the keys of this machine. */\nexport function sendShortcut(platform: string | undefined): string {\n return /mac|iphone|ipad/i.test(platform ?? '') ? '⌘ Enter' : 'Ctrl+Enter'\n}\n\n/**\n * A pasted table given back as words: what the clipboard held as text, or - when it held only HTML - the rows with\n * a tab between cells, as a spreadsheet copies them.\n */\nexport function tableAsWords(table: { rows: string[][]; text?: string | null }): string {\n const text = (table.text ?? '').replace(/\\r\\n?/g, '\\n').replace(/\\n+$/, '')\n return text.trim() ? text : table.rows.map(row => row.join('\\t')).join('\\n')\n}\n\n/** The words appended to what is already written - on a line of their own. */\nexport function appendWords(written: string, words: string): string {\n return written.trim() ? `${written.replace(/\\s+$/, '')}\\n${words}` : words\n}\n\n/**\n * The request the empty box suggests, which a Tab writes in (design/ai/screen-pattern.md §4-1). Before anything is\n * said it is the conversation's first example request, its own before the host's; after an answer it is the next\n * request that answer named. A placeholder is how to write, not a request, so it is never the suggestion.\n */\nexport function composerSuggestion(input: {\n started: boolean\n modeExamples?: Array<{ items: string[] }>\n hostExamples?: Array<{ items: string[] }>\n /** The next request the last answer named. */\n answered?: string\n}): string | undefined {\n if (input.started) return input.answered?.trim() || undefined\n const groups = input.modeExamples?.length ? input.modeExamples : input.hostExamples\n return groups?.find(group => group.items.length > 0)?.items[0]\n}\n"]}
@@ -27,3 +27,4 @@
27
27
  */
28
28
  export * from './service/index.js';
29
29
  import './tool-registry-boot-report.js';
30
+ import './service/assistant-mode/fallback-subjects.js';
@@ -30,4 +30,6 @@ const tslib_1 = require("tslib");
30
30
  */
31
31
  tslib_1.__exportStar(require("./service/index.js"), exports);
32
32
  require("./tool-registry-boot-report.js");
33
+ /* The site, an application's home and a board are named without an application's help (design/ai/screen-pattern.md §2-1). */
34
+ require("./service/assistant-mode/fallback-subjects.js");
33
35
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,6DAAkC;AAClC,0CAAuC","sourcesContent":["/**\n * `@things-factory/ai-assistant` — **AI 대화의 집.**\n *\n * 세션·메시지·참가자·인박스·구독. 도메인을 모른다: 보드도, 공장도, 자산도.\n *\n * ── 왜 board-ai 에서 나왔나 (2026-09-10) ────────────────────────────────────\n * 시작한 물음은 하나였다. **「왜 board-ai 에 종속되어 있는가.」** 공장에 「지금 몇 대 밀렸나」를\n * 묻는 화면이 보드 편집 권한을 요구했고, 그 문 뒤에 스물다섯이 있는데 쓰는 것은 하나였다.\n *\n * 재 보니 문제는 「공통이냐 아니냐」가 아니었다 — `board-ui` 를 지나 **아홉 개 앱**이 이미 이\n * 기계를 싣고 있었다. 이미 공통이었고, 이름과 게이트만 보드 편집기의 것이었다.\n *\n * ── 자르는 선 ───────────────────────────────────────────────────────────────\n * 물음 하나로 줄였다: **「이것이 보드가 없는 제품에서도 뜻이 있나.」**\n *\n * 여기 세션·메시지·참가자·인박스·구독 인증·앵커\n * board-ai 보드 편집기 — assistant · 패치 · 프리팹 · 컴포넌트 트리 · 스타일링\n *\n * `assistant.ts`(113KB)는 **안 옮겼다.** 거의 전부 보드 편집이다. 가장 큰 파일이 제자리에 있다는\n * 뜻이고, 그래서 이 분리는 생각보다 작았다.\n *\n * ── 의존은 한쪽이다 ─────────────────────────────────────────────────────────\n * `board-ai → ai-assistant`. 반대는 없다. 그것을 위해 두 변을 먼저 끊었다 — `ChatSession` 의\n * 보드 패치 관계와, 리졸버 안에 섞여 있던 보드 op 여섯.\n *\n * 설계 정본은 `things-factory/docs/design/ai-modules.md`.\n */\nexport * from './service/index.js'\nimport './tool-registry-boot-report.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,6DAAkC;AAClC,0CAAuC;AACvC,6HAA6H;AAC7H,yDAAsD","sourcesContent":["/**\n * `@things-factory/ai-assistant` — **AI 대화의 집.**\n *\n * 세션·메시지·참가자·인박스·구독. 도메인을 모른다: 보드도, 공장도, 자산도.\n *\n * ── 왜 board-ai 에서 나왔나 (2026-09-10) ────────────────────────────────────\n * 시작한 물음은 하나였다. **「왜 board-ai 에 종속되어 있는가.」** 공장에 「지금 몇 대 밀렸나」를\n * 묻는 화면이 보드 편집 권한을 요구했고, 그 문 뒤에 스물다섯이 있는데 쓰는 것은 하나였다.\n *\n * 재 보니 문제는 「공통이냐 아니냐」가 아니었다 — `board-ui` 를 지나 **아홉 개 앱**이 이미 이\n * 기계를 싣고 있었다. 이미 공통이었고, 이름과 게이트만 보드 편집기의 것이었다.\n *\n * ── 자르는 선 ───────────────────────────────────────────────────────────────\n * 물음 하나로 줄였다: **「이것이 보드가 없는 제품에서도 뜻이 있나.」**\n *\n * 여기 세션·메시지·참가자·인박스·구독 인증·앵커\n * board-ai 보드 편집기 — assistant · 패치 · 프리팹 · 컴포넌트 트리 · 스타일링\n *\n * `assistant.ts`(113KB)는 **안 옮겼다.** 거의 전부 보드 편집이다. 가장 큰 파일이 제자리에 있다는\n * 뜻이고, 그래서 이 분리는 생각보다 작았다.\n *\n * ── 의존은 한쪽이다 ─────────────────────────────────────────────────────────\n * `board-ai → ai-assistant`. 반대는 없다. 그것을 위해 두 변을 먼저 끊었다 — `ChatSession` 의\n * 보드 패치 관계와, 리졸버 안에 섞여 있던 보드 op 여섯.\n *\n * 설계 정본은 `things-factory/docs/design/ai-modules.md`.\n */\nexport * from './service/index.js'\nimport './tool-registry-boot-report.js'\n/* The site, an application's home and a board are named without an application's help (design/ai/screen-pattern.md §2-1). */\nimport './service/assistant-mode/fallback-subjects.js'\n"]}
@@ -18,6 +18,7 @@ declare class AssistantChatInput {
18
18
  }
19
19
  declare class AssistantChatOutput {
20
20
  reply: string;
21
+ suggestion?: string | null;
21
22
  clientId: string;
22
23
  agentId?: string;
23
24
  sessionId?: string;
@@ -54,6 +54,7 @@ const attachment_policy_js_1 = require("./chat-session/attachment-policy.js");
54
54
  const session_attachments_js_1 = require("./chat-session/session-attachments.js");
55
55
  const registry_js_1 = require("./assistant-mode/registry.js");
56
56
  const resolve_js_1 = require("./assistant-mode/resolve.js");
57
+ const screen_rules_js_1 = require("./assistant-mode/screen-rules.js");
57
58
  const request_translator_js_1 = require("./request-translator.js");
58
59
  /**
59
60
  * How many stored messages reach the model.
@@ -150,6 +151,13 @@ tslib_1.__decorate([
150
151
  (0, type_graphql_1.Field)({ description: 'The reply, including the notice when the loop stopped early.' }),
151
152
  tslib_1.__metadata("design:type", String)
152
153
  ], AssistantChatOutput.prototype, "reply", void 0);
154
+ tslib_1.__decorate([
155
+ (0, type_graphql_1.Field)(() => String, {
156
+ nullable: true,
157
+ description: 'The request the person is likely to make next, as the reply named it. The input shows it and Tab writes it in. Not stored with the reply.'
158
+ }),
159
+ tslib_1.__metadata("design:type", String)
160
+ ], AssistantChatOutput.prototype, "suggestion", void 0);
153
161
  tslib_1.__decorate([
154
162
  (0, type_graphql_1.Field)({ description: 'AI client identifier (provider:model).' }),
155
163
  tslib_1.__metadata("design:type", String)
@@ -289,6 +297,19 @@ let AssistantChatResolver = class AssistantChatResolver {
289
297
  session = found;
290
298
  }
291
299
  (0, station_session_guard_js_1.assertStationSession)(session, input.hostContext, t);
300
+ /*
301
+ * 열린 대상 — 서버가 저장한 앵커에서 턴마다 한 줄(design/ai/screen-pattern.md §2-1). 화면이 보낸 hostContext 는
302
+ * 쓰지 않는다. 대상이 바뀌면 다음 턴의 줄도 바뀌므로 옛 대상이 대화에 남지 않는다.
303
+ */
304
+ const subjectLine = session
305
+ ? await (0, ai_client_base_1.openSubjectLine)({
306
+ anchorType: session.anchorType,
307
+ anchorId: session.anchorId,
308
+ domain,
309
+ state: context.state
310
+ })
311
+ : undefined;
312
+ const systemPrompt = subjectLine ? `${settings.systemPrompt}\n\n${subjectLine}` : settings.systemPrompt;
292
313
  const lastUserMessage = input.messages[input.messages.length - 1];
293
314
  let userMessageId;
294
315
  /*
@@ -469,7 +490,7 @@ let AssistantChatResolver = class AssistantChatResolver {
469
490
  const result = await (0, ai_client_base_1.registryAssistantChat)({
470
491
  client,
471
492
  messages: conversation,
472
- systemPrompt: settings.systemPrompt,
493
+ systemPrompt,
473
494
  toolCategories: settings.toolCategories,
474
495
  /* 도구가 「어느 대화에서 불렸나」를 안다 — 서버의 값이다(ADR-0061 결정 3). */
475
496
  mode: { kind: input.mode.kind, ...(input.mode.target ? { target: input.mode.target } : {}) },
@@ -484,7 +505,7 @@ let AssistantChatResolver = class AssistantChatResolver {
484
505
  * 자체를 안 보게 된다.
485
506
  */
486
507
  groundingSources: (0, grounding_sources_js_1.groundingSourcesFor)({
487
- systemPrompt: settings.systemPrompt,
508
+ systemPrompt,
488
509
  messages: conversation,
489
510
  hostContext: input.hostContext
490
511
  }),
@@ -497,13 +518,15 @@ let AssistantChatResolver = class AssistantChatResolver {
497
518
  * 이 답이 지나온 문 — ADR-0073 판정 1. 행에도, 목록 미리보기에도 같은 값이 간다. 한쪽만 적으면
498
519
  * 가려진 답이 목록에는 그대로 뜬다.
499
520
  */
521
+ /* The reply ends with the next request when one is natural; the input offers it and the stored answer does not keep it (§4-1). */
522
+ const { reply, suggestion } = (0, screen_rules_js_1.splitNextRequest)(result.reply);
500
523
  const answerDoors = (0, message_doors_js_1.doorsForAnswer)((result.loop.toolUsages ?? []).map((usage) => usage?.name).filter(Boolean), name => (0, ai_client_base_1.findToolSpec)(name, settings.toolCategories)?.doors);
501
524
  let assistantMessageId;
502
525
  if (session) {
503
526
  const stored = await messageRepo.save({
504
527
  session: { id: session.id },
505
528
  role: 'assistant',
506
- content: result.reply,
529
+ content: reply,
507
530
  /*
508
531
  * 이 답이 지나온 문을 함께 적는다 — ADR-0073 판정 1. 방은 도메인 안에서 공개이므로, 이 문장이
509
532
  * 실어 나르는 값을 나중에 읽는 사람이 열 수 있는지 판정할 근거가 행에 있어야 한다. 적지 않으면
@@ -526,14 +549,14 @@ let AssistantChatResolver = class AssistantChatResolver {
526
549
  kind: 'message',
527
550
  anchorType: session.anchorType,
528
551
  anchorId: session.anchorId,
529
- preview: result.reply,
552
+ preview: reply,
530
553
  actorId: user?.id
531
554
  });
532
555
  await sessionRepo.update(session.id, {
533
556
  aiClientId: client.id,
534
557
  agentId: client.agent.id,
535
558
  updater: user,
536
- ...(0, session_inbox_js_1.activityPatch)(result.reply, new Date(), answerDoors)
559
+ ...(0, session_inbox_js_1.activityPatch)(reply, new Date(), answerDoors)
537
560
  });
538
561
  /*
539
562
  * 상한 밖으로 밀려난 앞부분을 요약으로 접는다 — **답을 돌려준 뒤**에 한다(사용자 대기 시간에
@@ -553,7 +576,8 @@ let AssistantChatResolver = class AssistantChatResolver {
553
576
  });
554
577
  }
555
578
  return {
556
- reply: result.reply,
579
+ reply,
580
+ suggestion: suggestion ?? null,
557
581
  clientId: client.id,
558
582
  agentId: client.agent.id,
559
583
  sessionId: session?.id,
@@ -1 +1 @@
1
- {"version":3,"file":"assistant-chat-resolver.js","sourceRoot":"","sources":["../../server/service/assistant-chat-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The neutral door into a conversation.\n *\n * ── Why this exists (2026-09-11) ───────────────────────────────────────────────\n * The chat component moved here from `board-ai`, but the only mutation it could call stayed\n * behind. operato-figure mounted the dock, registered four tools, and got\n * `Cannot query field \"boardAIChat\" on type \"Mutation\"` — it does not load board-ai. The twin has\n * the same shape and it does not show: its space deliberation runs through board-ai's door purely\n * because the board authoring half of the same product already loaded that package.\n *\n * ── Why the board resolver was not moved ───────────────────────────────────────\n * `board-ai-resolver.ts` is 692 lines and the larger part of it is board authoring itself:\n * `currentBoard` as a BoardModel, PatchEntry, board patch broadcasts, refid injection for\n * component mentions. Moving that here would teach this package the board document, which is the\n * dependency we just cut, pointing the other way.\n *\n * So the door moved and the editor did not. `boardAIChat` stays in board-ai for board authoring;\n * this one is for a surface that asks questions and runs registered tools.\n *\n * ── What this is not ───────────────────────────────────────────────────────────\n * It has no document, so it takes no document and returns no patch. Tools that write to a\n * document are not offered here — `registryAssistantChat` enforces that, and it decides what may\n * be dispatched from the same list it offers, so the two cannot drift apart.\n */\nimport { Arg, Ctx, Directive, Field, InputType, Mutation, ObjectType, Resolver } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\nimport '@things-factory/auth-base'\n\nimport { getRepository, rethrowSpoken } from '@things-factory/shell'\nimport { aiClientFor, findToolSpec, registryAssistantChat, type AgentClient } from '@things-factory/ai-client-base'\n\nimport { ChatSession } from './chat-session/chat-session.js'\nimport { assertStationSession } from './chat-session/station-session-guard.js'\nimport { ChatMessage } from './chat-message/chat-message.js'\nimport { buildLlmHistory } from './chat-message/llm-history.js'\nimport { splitByCap } from './chat-message/history-summary.js'\nimport { foldSessionHistory } from './chat-message/fold-session-history.js'\nimport { groundingSourcesFor } from './grounding-sources.js'\nimport { publishChatMessage } from './chat-message/chat-message-publish.js'\nimport { publishSessionActivity } from './chat-session/session-activity-publish.js'\nimport { activityPatch, autoSessionName } from './chat-session/session-inbox.js'\nimport { toolAccessFor } from './tool-access.js'\nimport { ChatSessionParticipant } from './chat-session-participant/chat-session-participant.js'\nimport { doorsForAnswer } from './chat-message/message-doors.js'\nimport { judgeSessionKind } from './chat-session/session-kind.js'\nimport { readerGate } from './chat-message/reader-gate.js'\nimport { inviteMentionedUsers } from './chat-session-participant/mention-invite.js'\nimport { buildInviteNotification } from './chat-session-participant/invite-notification.js'\nimport { isSessionParticipant } from './chat-session-participant/participant-lookup.js'\nimport { assertMayWriteSession } from './chat-session/session-write-gate.js'\nimport { attachmentHistoryLine, refuseAttachments, textCharsOf } from './chat-session/attachment-policy.js'\nimport { attachmentForModel, describeAttachment, findChatAttachment } from './chat-session/session-attachments.js'\nimport { getAssistantMode } from './assistant-mode/registry.js'\nimport { resolveModeSettings } from './assistant-mode/resolve.js'\nimport { requestTranslator } from './request-translator.js'\n\n/**\n * How many stored messages reach the model.\n *\n * A deliberation runs for days. Without a cap the prompt grows without bound and the oldest turns\n * bury the recent ones. Kept the same as the board conversation's cap so the two surfaces of one\n * product do not remember differently.\n */\nconst LLM_HISTORY_MAX_TURNS = 40\n\n@InputType()\nclass AssistantMessageInput {\n @Field({ description: \"Role: 'user' or 'assistant'\" })\n role: string\n\n @Field({ description: 'Message content' })\n content: string\n}\n\n@InputType({ description: 'Which conversation this is, and what it is about.' })\nclass AssistantModeInput {\n @Field({ description: 'A mode kind the app server registered — `<app>.<surface>`, e.g. \"twin.space\".' })\n kind: string\n\n @Field({\n nullable: true,\n description:\n '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.'\n })\n target?: string\n}\n\n@InputType({ description: 'One turn of a conversation that has no document of its own.' })\nclass AssistantChatInput {\n @Field({\n nullable: true,\n description: 'ChatSession id. Omit for a one-off question with no persistence.'\n })\n sessionId?: string\n\n @Field(() => [AssistantMessageInput], {\n description:\n 'Conversation, newest last. For a persisted session only the last user message is appended; the rest is read from storage.'\n })\n messages: AssistantMessageInput[]\n\n @Field(() => AssistantModeInput, {\n description:\n '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.'\n })\n mode: AssistantModeInput\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'What this surface is looking at (e.g. { figureId } while modelling). Tools read it so the model never has to ask the user for identifiers. Client-supplied — never used for authorization; a tool verifies the scope belongs to the caller tenant.'\n })\n hostContext?: any\n\n @Field({\n nullable: true,\n description:\n 'Last message to keep from the stored history. The client keeps its own truncation when a user edits a message, so pass the last kept id to hide the retracted span from the model.'\n })\n truncateAfterMessageId?: string\n\n @Field(() => [String], {\n nullable: true,\n description: 'Files stored with this conversation by attachToConversation, sent with this turn.'\n })\n attachmentIds?: string[]\n\n @Field({\n nullable: true,\n description:\n 'The AI agent the person picked for this turn, from aiAgentChoices. Omitted takes the domain default. An agent this domain cannot use is refused - the provider, the model and the key are always the server\\'s.'\n })\n agentId?: string\n}\n\n@ObjectType()\nclass AssistantChatOutput {\n @Field({ description: 'The reply, including the notice when the loop stopped early.' })\n reply: string\n\n @Field({ description: 'AI client identifier (provider:model).' })\n clientId: string\n\n @Field({ nullable: true, description: 'The AI agent that answered.' })\n agentId?: string\n\n @Field({ nullable: true, description: 'Echo of the session id, when persisted.' })\n sessionId?: string\n\n @Field({ nullable: true, description: 'Stored ChatMessage id of the user input.' })\n userMessageId?: string\n\n @Field({ nullable: true, description: 'Stored ChatMessage id of the reply.' })\n assistantMessageId?: string\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n '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.'\n })\n historyFolded?: any\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description: 'Tool usages during the turn — {name, arguments, result, kind}. The surface shows them foldably.'\n })\n toolUsages?: any\n\n @Field(() => [String], {\n nullable: true,\n description:\n 'Tool names offered to the model this turn. A surface that expected a category and sees it missing here knows the registration did not happen, rather than reading a vague answer as a refusal.'\n })\n offeredTools?: string[]\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Actions a tool recorded without executing — each carries what the tool returned alongside `proposed: true`. The surface renders a confirm button; executing is the user\\'s act.'\n })\n proposals?: any\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Identifiers in the reply that appear in nothing the model received. Hallucination candidates surfaced to the user; the reply itself is not altered. Null when the answer is grounded.'\n })\n groundingWarnings?: any\n}\n\n@Resolver()\nexport class AssistantChatResolver {\n /*\n * No `@transaction`, for the same reason the board conversation has none: a model call takes\n * seconds to tens of seconds, and holding a write lock for that long collides with everything\n * else on SQLite. Writes are short and around the call. For a conversation, a partly stored\n * history is safer than a lost one.\n */\n @Mutation(() => AssistantChatOutput, {\n description:\n 'Ask the assistant a question on a surface that has no document of its own. Tools come from the registry; with a sessionId the conversation is persisted.'\n })\n /*\n * `ai-assistant:mutation` — the same door as the board conversation's, which moved here on\n * 2026-09-10. No `legacy` alias: this operation is new, so no one was ever passing under an old\n * name, and declaring an alias would open a second name that nothing needs.\n */\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"mutation\", domainOwnerGranted: true)')\n async assistantChat(@Arg('input') input: AssistantChatInput, @Ctx() context: any): Promise<AssistantChatOutput> {\n const { domain, user } = context.state\n\n /*\n * The domain's agent - the one picked, or the default (design/ai/agent-settings.md §4). Asked before anything is\n * stored: a domain with no AI, or an agent it cannot use, is refused as itself.\n */\n let client: AgentClient\n try {\n client = await aiClientFor({\n domain,\n user,\n agentId: input.agentId,\n modeKind: input.mode.kind,\n sessionId: input.sessionId\n })\n } catch (error) {\n rethrowSpoken(error, context)\n }\n /* A tool that asks a model of its own within this turn answers with the same agent (ToolCallContext.state). */\n context.state.aiAgentId = client.agent.id\n\n /*\n * Read the declaration before anything is stored. A kind this server does not have is a\n * mismatched deployment, and the person should see that instead of a conversation with no\n * persona and no tools.\n */\n const t = requestTranslator(context)\n const settings = resolveModeSettings(input.mode, t)\n\n const sessionRepo = getRepository(ChatSession)\n const messageRepo = getRepository(ChatMessage)\n\n let session: ChatSession | undefined\n if (input.sessionId) {\n const found = await sessionRepo.findOneBy({ id: input.sessionId, domain: { id: domain.id } as any })\n if (!found) throw new Error(`ChatSession ${input.sessionId} not found`)\n /*\n * A private conversation is written by its people only - and here more than anywhere: the answer is\n * built on this conversation's history, so an outsider's question would read it back to them.\n */\n await assertMayWriteSession({\n session: found as any,\n userId: user?.id,\n isParticipant: () => isSessionParticipant(domain.id, found.id!, user?.id),\n t: (key, options) => t(key, options as any)\n })\n if (found.anchorType === 'figure' && input.hostContext?.figureId !== found.anchorId) {\n throw new Error(\n context.t('ai-assistant.text.other-figure-history', {\n defaultValue: 'this conversation belongs to another figure. open the conversation of the model you have open.'\n })\n )\n }\n if (found.anchorType === 'application' && (input.hostContext?.app !== found.anchorId || input.hostContext?.figureId)) {\n throw new Error(\n context.t('ai-assistant.text.application-vs-model-history', {\n defaultValue: 'this conversation is about the application, not about a model. open the one you want.'\n })\n )\n }\n /*\n * 이 세션이 **이 대화의** 세션인가 — ADR-0072 결정 3. 다른 대화의 세션에 말을 붙이면 그 이력이\n * 이 대화의 프롬프트로 들어가고, 화면은 줄이 사라져 새 대화처럼 보이므로 보낸 사람은 모른다.\n */\n const verdict = judgeSessionKind((found as any).kind, input.mode.kind)\n if (verdict.action === 'refuse') {\n throw new Error(\n t('ai-assistant.text.session-kind-mismatch', {\n ...verdict.params,\n defaultValue: 'this conversation is {sessionKind}, and you are writing as {requested}. open the conversation you mean.'\n })\n )\n }\n if (verdict.action === 'stamp') {\n /* 칸이 서기 전에 난 세션 — 요청한 대화로 한 번 찍는다. 다음부터는 위 규칙 하나만 남는다. */\n await sessionRepo.update(found.id!, { kind: verdict.kind } as any)\n ;(found as any).kind = verdict.kind\n }\n session = found\n }\n\n assertStationSession(session, input.hostContext, t)\n\n const lastUserMessage = input.messages[input.messages.length - 1]\n let userMessageId: string | undefined\n\n /*\n * Files sent with this turn (design/ai/shared-dock.md §9). They were stored with this conversation by\n * attachToConversation; one that is not this conversation's is refused, never read. The policy is the\n * mode's - checked again here, since the door is the authority, not the screen.\n */\n const attachmentRows: any[] = []\n if (input.attachmentIds?.length) {\n if (!session) throw new Error(t('ai-assistant.text.attachments-need-session', { defaultValue: 'attachments need a saved conversation.' }))\n for (const id of input.attachmentIds) {\n const row = await findChatAttachment(domain.id, session.id!, id)\n if (!row) throw new Error(t('ai-assistant.text.attachment-not-in-session', { defaultValue: 'an attachment does not belong to this conversation.' }))\n attachmentRows.push(row)\n }\n const refusal = refuseAttachments(\n getAssistantMode(input.mode.kind)?.attachments,\n attachmentRows.map(row => ({ name: row.name, type: row.mimetype, size: Number(row.size) }))\n )\n if (refusal) throw new Error(t(`ai-assistant.text.${refusal.code}`, { ...(refusal as any), defaultValue: refusal.code }))\n }\n\n if (session && lastUserMessage?.role === 'user') {\n const previousLast = await messageRepo.findOne({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'DESC' }\n })\n const saved = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'user',\n content: lastUserMessage.content,\n /*\n * This door is the one people call to ask the assistant, so a turn stored here was for the\n * assistant (ADR-0070 A15 decision ①). The value comes from which door was called, never\n * from the request — two sources for one fact can disagree.\n */\n addressee: 'assistant',\n attachments: attachmentRows.length ? JSON.stringify(attachmentRows.map(describeAttachment)) : undefined,\n creator: user,\n updater: user,\n parentMessage: previousLast ? ({ id: previousLast.id } as any) : undefined\n } as any)\n userMessageId = saved.id\n\n /*\n * Session activity is denormalised for the list: most-recent ordering and another\n * participant's unread badge read it. Best effort — a conversation must not fail because a\n * list sorts one position late.\n */\n try {\n const patch: any = activityPatch(lastUserMessage.content)\n if (!session.name) patch.name = autoSessionName(lastUserMessage.content)\n await sessionRepo.update(session.id!, patch)\n } catch {\n /* noop */\n }\n\n /*\n * 사람이 `@이름` 으로 부른 사람은 이 대화에 들어온다(ADR-0071 결정 1). 이 문은 **사람의 턴**을\n * 저장하는 문이라 그 일을 한다 — 모델의 답이나 도구 결과는 아무도 초대하지 않는다.\n */\n const invite = await inviteMentionedUsers({\n content: lastUserMessage.content,\n sessionId: session.id!,\n domainId: domain.id,\n inviter: user,\n participants: getRepository(ChatSessionParticipant),\n visibility: (session as any).visibility\n })\n if (invite.invited.length > 0) {\n const payload = buildInviteNotification(invite.invited, undefined, user, (session as any)?.name)\n try {\n if (payload) await (context.state as any)?.notify?.(payload)\n } catch {\n /* 편지가 못 갔다고 방에 들어온 사실이 취소되지는 않는다. */\n }\n }\n\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: saved } as any)\n publishSessionActivity({\n domainId: domain.id,\n sessionId: session.id!,\n kind: 'message',\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n preview: lastUserMessage.content,\n actorId: user?.id\n })\n }\n\n /*\n * 이번 턴에 상한 밖으로 밀려난 것이 있는가 — 화면이 「대화가 길어졌다」를 알릴 근거다.\n *\n * 이 문은 그 사실을 내지 않았다. 대화창에는 그 안내를 그리는 자리가 있고(`historyFolded`), 보드\n * 문만 그 값을 채웠다. 그래서 앱의 대화는 앞부분이 잘려 나가도 **사람에게는 아무 말도 하지 않았다** —\n * 모델에게는 「N 개 생략」을 적어 보내면서.\n */\n const readRows = session\n ? await messageRepo.find({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'ASC' },\n relations: ['creator']\n })\n : []\n /*\n * 모델이 읽는 이력도 **부른 사람** 기준으로 가린다 — ADR-0073 판정 1. 가리지 않으면, 문을 한 번도\n * 안 지난 사람이 \"아까 그 수 다시 말해 줘\" 한 마디로 값을 가져간다. 모델이 옮겨 적는 순간 그 값은\n * 문 없는 새 답이 된다. 가린 자리를 지우지 않고 한 줄로 남기는 이유도 같다 — 지우면 모델이 그런\n * 말이 없었다고 답하고, 그것은 거짓말이다.\n */\n const storedRows = await readerGate(context)(readRows)\n const capped = splitByCap(\n storedRows.map(row => ({ id: row.id, role: row.role, content: row.content ?? '' })),\n LLM_HISTORY_MAX_TURNS\n )\n const historyFolded =\n capped.dropped.length > 0\n ? { omitted: capped.dropped.length, summarized: Boolean((session as any)?.lastSummary) }\n : undefined\n\n const conversation = session\n ? buildLlmHistory(\n /*\n * The model reads who spoke and who they spoke to. The entity carries the speaker as a\n * relation and the addressee as a column; the history wants them as plain fields, and\n * without this mapping every line arrived unattributed — a room of people read as one\n * voice (ADR-0070 A15 decision ②).\n */\n storedRows.map(message => ({\n id: message.id,\n role: message.role,\n /*\n * A file sent in an earlier turn is not sent again - one line names it, and the model opens it\n * with readChatAttachment when it needs to (cost is a usage axis, ADR-0061 decision 6).\n */\n content:\n message.id !== userMessageId && (message as any).attachmentsJson?.length\n ? [message.content, ...(message as any).attachmentsJson.map(attachmentHistoryLine)].join('\\n')\n : message.content,\n senderId: (message as any).creator?.id,\n senderName: (message as any).creator?.name,\n addressee: (message as any).addressee\n })),\n {\n maxTurns: LLM_HISTORY_MAX_TURNS,\n truncateAfterMessageId: input.truncateAfterMessageId,\n /* The message just stored is this turn's question — truncation must never drop it. */\n keepMessageIds: userMessageId ? [userMessageId] : undefined,\n summary: (session as any).lastSummary\n ? { text: (session as any).lastSummary, upToMessageId: (session as any).summaryUpToMessageId }\n : undefined\n }\n )\n : input.messages.map(message => ({ role: message.role as any, content: message.content }))\n\n if (attachmentRows.length && conversation.length > 0) {\n const last = conversation[conversation.length - 1]\n if (last.role === 'user' && typeof last.content === 'string') {\n const parts: any[] = [{ type: 'text', text: last.content }]\n for (const row of attachmentRows) {\n const file = await attachmentForModel(row, textCharsOf(getAssistantMode(input.mode?.kind ?? '')?.attachments))\n if (file.type === 'image') {\n parts.push({ type: 'image', source: { kind: 'base64', data: file.data, mediaType: file.mediaType } })\n } else {\n parts.push({\n type: 'text',\n text: `[attachment ${row.id}: ${file.name}${file.truncated ? ' - cut, the file is longer' : ''}]\\n${file.text}`\n })\n }\n }\n last.content = parts as any\n }\n } else if (input.hostContext?.image?.data && conversation.length > 0) {\n /* The screen-image path (figure's rendered view) - not a file of the conversation. */\n const last = conversation[conversation.length - 1]\n if (last.role === 'user' && typeof last.content === 'string') {\n const raw = input.hostContext.image\n const rawData = typeof raw.data === 'string' ? raw.data.replace(/^data:image\\/[a-zA-Z]+;base64,/, '') : raw.data\n const buf = typeof rawData === 'string' ? Buffer.from(rawData, 'base64') : Buffer.from(rawData)\n last.content = [\n { type: 'text', text: last.content },\n { type: 'image', source: { kind: 'base64', data: buf, mediaType: raw.mediaType || 'image/png' } }\n ] as any\n }\n }\n\n const result = await registryAssistantChat({\n client,\n messages: conversation as any,\n systemPrompt: settings.systemPrompt,\n toolCategories: settings.toolCategories,\n /* 도구가 「어느 대화에서 불렸나」를 안다 — 서버의 값이다(ADR-0061 결정 3). */\n mode: { kind: input.mode.kind, ...(input.mode.target ? { target: input.mode.target } : {}) },\n toolAccess: toolAccessFor(context),\n /* The stop notice is a sentence a person reads — the server writes it in their language (ADR-0054). */\n t,\n hostContext: input.hostContext,\n /*\n * 근거는 도구 결과만이 아니다 — 시스템 프롬프트 · 이 대화의 말 · 화면이 보낸 스냅숏도 근거다\n * (`ai-client-base/utils/grounding`). 이 문은 그 셋을 넘기지 않아, **사람이 방금 타이핑한\n * 식별자**를 되받아 적은 답이 「근거 없음」으로 표시됐다. 그런 경고가 몇 번 나오면 사람이 경고\n * 자체를 안 보게 된다.\n */\n groundingSources: groundingSourcesFor({\n systemPrompt: settings.systemPrompt,\n messages: conversation as any,\n hostContext: input.hostContext\n }),\n state: context.state,\n temperature: settings.temperature,\n maxTokens: settings.maxTokens,\n requireToolOnFirstTurn: settings.requireToolOnFirstTurn\n })\n\n /*\n * 이 답이 지나온 문 — ADR-0073 판정 1. 행에도, 목록 미리보기에도 같은 값이 간다. 한쪽만 적으면\n * 가려진 답이 목록에는 그대로 뜬다.\n */\n const answerDoors = doorsForAnswer(\n (result.loop.toolUsages ?? []).map((usage: any) => usage?.name).filter(Boolean),\n name => (findToolSpec(name, settings.toolCategories) as any)?.doors\n )\n\n let assistantMessageId: string | undefined\n if (session) {\n const stored = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'assistant',\n content: result.reply,\n /*\n * 이 답이 지나온 문을 함께 적는다 — ADR-0073 판정 1. 방은 도메인 안에서 공개이므로, 이 문장이\n * 실어 나르는 값을 나중에 읽는 사람이 열 수 있는지 판정할 근거가 행에 있어야 한다. 적지 않으면\n * 빈 목록이 되고, 빈 목록은 누구나 읽는다는 뜻이다.\n */\n doors: answerDoors,\n toolUsages: result.loop.toolUsages?.length ? JSON.stringify(result.loop.toolUsages) : undefined,\n proposals: result.loop.proposals?.length ? JSON.stringify(result.loop.proposals) : undefined,\n groundingWarnings: result.groundingWarnings?.length ? JSON.stringify(result.groundingWarnings) : undefined,\n creator: user,\n updater: user,\n parentMessage: userMessageId ? ({ id: userMessageId } as any) : undefined\n })\n assistantMessageId = stored.id\n\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: stored } as any)\n /* The reply is session activity too. Leaving it out shows other participants \"nothing new\". */\n publishSessionActivity({\n domainId: domain.id,\n sessionId: session.id!,\n kind: 'message',\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n preview: result.reply,\n actorId: user?.id\n })\n\n await sessionRepo.update(session.id!, {\n aiClientId: client.id,\n agentId: client.agent.id,\n updater: user,\n ...activityPatch(result.reply, new Date(), answerDoors)\n } as any)\n\n /*\n * 상한 밖으로 밀려난 앞부분을 요약으로 접는다 — **답을 돌려준 뒤**에 한다(사용자 대기 시간에\n * 요약 호출을 얹지 않는다). 기다리지 않으므로 실패해도 대화에는 영향이 없다: 요약이 없으면 다음\n * 턴이 「N 개 생략」으로 정직하게 알린다.\n *\n * 이 문에는 이 호출이 없었다. 조각은 다 있었는데(상한 · 생략 알림 · `lastSummary` · 순수 규칙)\n * 부르는 쪽이 보드 제품 하나뿐이어서, 앱의 대화는 며칠이 지나도 앞부분을 요약하지 않고 매 턴\n * 「생략했다」고만 말했다 — 협의가 길어질수록 처음에 합의한 것이 먼저 사라진다.\n */\n void foldSessionHistory({\n sessionId: session.id!,\n domainId: domain.id,\n client,\n maxTurns: LLM_HISTORY_MAX_TURNS,\n locale: context.state?.lng\n })\n }\n\n return {\n reply: result.reply,\n clientId: client.id,\n agentId: client.agent.id,\n sessionId: session?.id,\n userMessageId,\n assistantMessageId,\n toolUsages: result.loop.toolUsages?.length ? result.loop.toolUsages : null,\n offeredTools: result.offeredTools,\n proposals: result.loop.proposals?.length ? result.loop.proposals : null,\n groundingWarnings: result.groundingWarnings?.length ? result.groundingWarnings : null,\n historyFolded: historyFolded ?? null\n }\n }\n}\n"]}
1
+ {"version":3,"file":"assistant-chat-resolver.js","sourceRoot":"","sources":["../../server/service/assistant-chat-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The neutral door into a conversation.\n *\n * ── Why this exists (2026-09-11) ───────────────────────────────────────────────\n * The chat component moved here from `board-ai`, but the only mutation it could call stayed\n * behind. operato-figure mounted the dock, registered four tools, and got\n * `Cannot query field \"boardAIChat\" on type \"Mutation\"` — it does not load board-ai. The twin has\n * the same shape and it does not show: its space deliberation runs through board-ai's door purely\n * because the board authoring half of the same product already loaded that package.\n *\n * ── Why the board resolver was not moved ───────────────────────────────────────\n * `board-ai-resolver.ts` is 692 lines and the larger part of it is board authoring itself:\n * `currentBoard` as a BoardModel, PatchEntry, board patch broadcasts, refid injection for\n * component mentions. Moving that here would teach this package the board document, which is the\n * dependency we just cut, pointing the other way.\n *\n * So the door moved and the editor did not. `boardAIChat` stays in board-ai for board authoring;\n * this one is for a surface that asks questions and runs registered tools.\n *\n * ── What this is not ───────────────────────────────────────────────────────────\n * It has no document, so it takes no document and returns no patch. Tools that write to a\n * document are not offered here — `registryAssistantChat` enforces that, and it decides what may\n * be dispatched from the same list it offers, so the two cannot drift apart.\n */\nimport { Arg, Ctx, Directive, Field, InputType, Mutation, ObjectType, Resolver } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\nimport '@things-factory/auth-base'\n\nimport { getRepository, rethrowSpoken } from '@things-factory/shell'\nimport { aiClientFor, findToolSpec, openSubjectLine, registryAssistantChat, type AgentClient } from '@things-factory/ai-client-base'\n\nimport { ChatSession } from './chat-session/chat-session.js'\nimport { assertStationSession } from './chat-session/station-session-guard.js'\nimport { ChatMessage } from './chat-message/chat-message.js'\nimport { buildLlmHistory } from './chat-message/llm-history.js'\nimport { splitByCap } from './chat-message/history-summary.js'\nimport { foldSessionHistory } from './chat-message/fold-session-history.js'\nimport { groundingSourcesFor } from './grounding-sources.js'\nimport { publishChatMessage } from './chat-message/chat-message-publish.js'\nimport { publishSessionActivity } from './chat-session/session-activity-publish.js'\nimport { activityPatch, autoSessionName } from './chat-session/session-inbox.js'\nimport { toolAccessFor } from './tool-access.js'\nimport { ChatSessionParticipant } from './chat-session-participant/chat-session-participant.js'\nimport { doorsForAnswer } from './chat-message/message-doors.js'\nimport { judgeSessionKind } from './chat-session/session-kind.js'\nimport { readerGate } from './chat-message/reader-gate.js'\nimport { inviteMentionedUsers } from './chat-session-participant/mention-invite.js'\nimport { buildInviteNotification } from './chat-session-participant/invite-notification.js'\nimport { isSessionParticipant } from './chat-session-participant/participant-lookup.js'\nimport { assertMayWriteSession } from './chat-session/session-write-gate.js'\nimport { attachmentHistoryLine, refuseAttachments, textCharsOf } from './chat-session/attachment-policy.js'\nimport { attachmentForModel, describeAttachment, findChatAttachment } from './chat-session/session-attachments.js'\nimport { getAssistantMode } from './assistant-mode/registry.js'\nimport { resolveModeSettings } from './assistant-mode/resolve.js'\nimport { splitNextRequest } from './assistant-mode/screen-rules.js'\nimport { requestTranslator } from './request-translator.js'\n\n/**\n * How many stored messages reach the model.\n *\n * A deliberation runs for days. Without a cap the prompt grows without bound and the oldest turns\n * bury the recent ones. Kept the same as the board conversation's cap so the two surfaces of one\n * product do not remember differently.\n */\nconst LLM_HISTORY_MAX_TURNS = 40\n\n@InputType()\nclass AssistantMessageInput {\n @Field({ description: \"Role: 'user' or 'assistant'\" })\n role: string\n\n @Field({ description: 'Message content' })\n content: string\n}\n\n@InputType({ description: 'Which conversation this is, and what it is about.' })\nclass AssistantModeInput {\n @Field({ description: 'A mode kind the app server registered — `<app>.<surface>`, e.g. \"twin.space\".' })\n kind: string\n\n @Field({\n nullable: true,\n description:\n '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.'\n })\n target?: string\n}\n\n@InputType({ description: 'One turn of a conversation that has no document of its own.' })\nclass AssistantChatInput {\n @Field({\n nullable: true,\n description: 'ChatSession id. Omit for a one-off question with no persistence.'\n })\n sessionId?: string\n\n @Field(() => [AssistantMessageInput], {\n description:\n 'Conversation, newest last. For a persisted session only the last user message is appended; the rest is read from storage.'\n })\n messages: AssistantMessageInput[]\n\n @Field(() => AssistantModeInput, {\n description:\n '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.'\n })\n mode: AssistantModeInput\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'What this surface is looking at (e.g. { figureId } while modelling). Tools read it so the model never has to ask the user for identifiers. Client-supplied — never used for authorization; a tool verifies the scope belongs to the caller tenant.'\n })\n hostContext?: any\n\n @Field({\n nullable: true,\n description:\n 'Last message to keep from the stored history. The client keeps its own truncation when a user edits a message, so pass the last kept id to hide the retracted span from the model.'\n })\n truncateAfterMessageId?: string\n\n @Field(() => [String], {\n nullable: true,\n description: 'Files stored with this conversation by attachToConversation, sent with this turn.'\n })\n attachmentIds?: string[]\n\n @Field({\n nullable: true,\n description:\n 'The AI agent the person picked for this turn, from aiAgentChoices. Omitted takes the domain default. An agent this domain cannot use is refused - the provider, the model and the key are always the server\\'s.'\n })\n agentId?: string\n}\n\n@ObjectType()\nclass AssistantChatOutput {\n @Field({ description: 'The reply, including the notice when the loop stopped early.' })\n reply: string\n\n @Field(() => String, {\n nullable: true,\n description:\n 'The request the person is likely to make next, as the reply named it. The input shows it and Tab writes it in. Not stored with the reply.'\n })\n suggestion?: string | null\n\n @Field({ description: 'AI client identifier (provider:model).' })\n clientId: string\n\n @Field({ nullable: true, description: 'The AI agent that answered.' })\n agentId?: string\n\n @Field({ nullable: true, description: 'Echo of the session id, when persisted.' })\n sessionId?: string\n\n @Field({ nullable: true, description: 'Stored ChatMessage id of the user input.' })\n userMessageId?: string\n\n @Field({ nullable: true, description: 'Stored ChatMessage id of the reply.' })\n assistantMessageId?: string\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n '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.'\n })\n historyFolded?: any\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description: 'Tool usages during the turn — {name, arguments, result, kind}. The surface shows them foldably.'\n })\n toolUsages?: any\n\n @Field(() => [String], {\n nullable: true,\n description:\n 'Tool names offered to the model this turn. A surface that expected a category and sees it missing here knows the registration did not happen, rather than reading a vague answer as a refusal.'\n })\n offeredTools?: string[]\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Actions a tool recorded without executing — each carries what the tool returned alongside `proposed: true`. The surface renders a confirm button; executing is the user\\'s act.'\n })\n proposals?: any\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Identifiers in the reply that appear in nothing the model received. Hallucination candidates surfaced to the user; the reply itself is not altered. Null when the answer is grounded.'\n })\n groundingWarnings?: any\n}\n\n@Resolver()\nexport class AssistantChatResolver {\n /*\n * No `@transaction`, for the same reason the board conversation has none: a model call takes\n * seconds to tens of seconds, and holding a write lock for that long collides with everything\n * else on SQLite. Writes are short and around the call. For a conversation, a partly stored\n * history is safer than a lost one.\n */\n @Mutation(() => AssistantChatOutput, {\n description:\n 'Ask the assistant a question on a surface that has no document of its own. Tools come from the registry; with a sessionId the conversation is persisted.'\n })\n /*\n * `ai-assistant:mutation` — the same door as the board conversation's, which moved here on\n * 2026-09-10. No `legacy` alias: this operation is new, so no one was ever passing under an old\n * name, and declaring an alias would open a second name that nothing needs.\n */\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"mutation\", domainOwnerGranted: true)')\n async assistantChat(@Arg('input') input: AssistantChatInput, @Ctx() context: any): Promise<AssistantChatOutput> {\n const { domain, user } = context.state\n\n /*\n * The domain's agent - the one picked, or the default (design/ai/agent-settings.md §4). Asked before anything is\n * stored: a domain with no AI, or an agent it cannot use, is refused as itself.\n */\n let client: AgentClient\n try {\n client = await aiClientFor({\n domain,\n user,\n agentId: input.agentId,\n modeKind: input.mode.kind,\n sessionId: input.sessionId\n })\n } catch (error) {\n rethrowSpoken(error, context)\n }\n /* A tool that asks a model of its own within this turn answers with the same agent (ToolCallContext.state). */\n context.state.aiAgentId = client.agent.id\n\n /*\n * Read the declaration before anything is stored. A kind this server does not have is a\n * mismatched deployment, and the person should see that instead of a conversation with no\n * persona and no tools.\n */\n const t = requestTranslator(context)\n const settings = resolveModeSettings(input.mode, t)\n\n const sessionRepo = getRepository(ChatSession)\n const messageRepo = getRepository(ChatMessage)\n\n let session: ChatSession | undefined\n if (input.sessionId) {\n const found = await sessionRepo.findOneBy({ id: input.sessionId, domain: { id: domain.id } as any })\n if (!found) throw new Error(`ChatSession ${input.sessionId} not found`)\n /*\n * A private conversation is written by its people only - and here more than anywhere: the answer is\n * built on this conversation's history, so an outsider's question would read it back to them.\n */\n await assertMayWriteSession({\n session: found as any,\n userId: user?.id,\n isParticipant: () => isSessionParticipant(domain.id, found.id!, user?.id),\n t: (key, options) => t(key, options as any)\n })\n if (found.anchorType === 'figure' && input.hostContext?.figureId !== found.anchorId) {\n throw new Error(\n context.t('ai-assistant.text.other-figure-history', {\n defaultValue: 'this conversation belongs to another figure. open the conversation of the model you have open.'\n })\n )\n }\n if (found.anchorType === 'application' && (input.hostContext?.app !== found.anchorId || input.hostContext?.figureId)) {\n throw new Error(\n context.t('ai-assistant.text.application-vs-model-history', {\n defaultValue: 'this conversation is about the application, not about a model. open the one you want.'\n })\n )\n }\n /*\n * 이 세션이 **이 대화의** 세션인가 — ADR-0072 결정 3. 다른 대화의 세션에 말을 붙이면 그 이력이\n * 이 대화의 프롬프트로 들어가고, 화면은 줄이 사라져 새 대화처럼 보이므로 보낸 사람은 모른다.\n */\n const verdict = judgeSessionKind((found as any).kind, input.mode.kind)\n if (verdict.action === 'refuse') {\n throw new Error(\n t('ai-assistant.text.session-kind-mismatch', {\n ...verdict.params,\n defaultValue: 'this conversation is {sessionKind}, and you are writing as {requested}. open the conversation you mean.'\n })\n )\n }\n if (verdict.action === 'stamp') {\n /* 칸이 서기 전에 난 세션 — 요청한 대화로 한 번 찍는다. 다음부터는 위 규칙 하나만 남는다. */\n await sessionRepo.update(found.id!, { kind: verdict.kind } as any)\n ;(found as any).kind = verdict.kind\n }\n session = found\n }\n\n assertStationSession(session, input.hostContext, t)\n\n /*\n * 열린 대상 — 서버가 저장한 앵커에서 턴마다 한 줄(design/ai/screen-pattern.md §2-1). 화면이 보낸 hostContext 는\n * 쓰지 않는다. 대상이 바뀌면 다음 턴의 줄도 바뀌므로 옛 대상이 대화에 남지 않는다.\n */\n const subjectLine = session\n ? await openSubjectLine({\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n domain,\n state: context.state\n })\n : undefined\n const systemPrompt = subjectLine ? `${settings.systemPrompt}\\n\\n${subjectLine}` : settings.systemPrompt\n\n const lastUserMessage = input.messages[input.messages.length - 1]\n let userMessageId: string | undefined\n\n /*\n * Files sent with this turn (design/ai/shared-dock.md §9). They were stored with this conversation by\n * attachToConversation; one that is not this conversation's is refused, never read. The policy is the\n * mode's - checked again here, since the door is the authority, not the screen.\n */\n const attachmentRows: any[] = []\n if (input.attachmentIds?.length) {\n if (!session) throw new Error(t('ai-assistant.text.attachments-need-session', { defaultValue: 'attachments need a saved conversation.' }))\n for (const id of input.attachmentIds) {\n const row = await findChatAttachment(domain.id, session.id!, id)\n if (!row) throw new Error(t('ai-assistant.text.attachment-not-in-session', { defaultValue: 'an attachment does not belong to this conversation.' }))\n attachmentRows.push(row)\n }\n const refusal = refuseAttachments(\n getAssistantMode(input.mode.kind)?.attachments,\n attachmentRows.map(row => ({ name: row.name, type: row.mimetype, size: Number(row.size) }))\n )\n if (refusal) throw new Error(t(`ai-assistant.text.${refusal.code}`, { ...(refusal as any), defaultValue: refusal.code }))\n }\n\n if (session && lastUserMessage?.role === 'user') {\n const previousLast = await messageRepo.findOne({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'DESC' }\n })\n const saved = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'user',\n content: lastUserMessage.content,\n /*\n * This door is the one people call to ask the assistant, so a turn stored here was for the\n * assistant (ADR-0070 A15 decision ①). The value comes from which door was called, never\n * from the request — two sources for one fact can disagree.\n */\n addressee: 'assistant',\n attachments: attachmentRows.length ? JSON.stringify(attachmentRows.map(describeAttachment)) : undefined,\n creator: user,\n updater: user,\n parentMessage: previousLast ? ({ id: previousLast.id } as any) : undefined\n } as any)\n userMessageId = saved.id\n\n /*\n * Session activity is denormalised for the list: most-recent ordering and another\n * participant's unread badge read it. Best effort — a conversation must not fail because a\n * list sorts one position late.\n */\n try {\n const patch: any = activityPatch(lastUserMessage.content)\n if (!session.name) patch.name = autoSessionName(lastUserMessage.content)\n await sessionRepo.update(session.id!, patch)\n } catch {\n /* noop */\n }\n\n /*\n * 사람이 `@이름` 으로 부른 사람은 이 대화에 들어온다(ADR-0071 결정 1). 이 문은 **사람의 턴**을\n * 저장하는 문이라 그 일을 한다 — 모델의 답이나 도구 결과는 아무도 초대하지 않는다.\n */\n const invite = await inviteMentionedUsers({\n content: lastUserMessage.content,\n sessionId: session.id!,\n domainId: domain.id,\n inviter: user,\n participants: getRepository(ChatSessionParticipant),\n visibility: (session as any).visibility\n })\n if (invite.invited.length > 0) {\n const payload = buildInviteNotification(invite.invited, undefined, user, (session as any)?.name)\n try {\n if (payload) await (context.state as any)?.notify?.(payload)\n } catch {\n /* 편지가 못 갔다고 방에 들어온 사실이 취소되지는 않는다. */\n }\n }\n\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: saved } as any)\n publishSessionActivity({\n domainId: domain.id,\n sessionId: session.id!,\n kind: 'message',\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n preview: lastUserMessage.content,\n actorId: user?.id\n })\n }\n\n /*\n * 이번 턴에 상한 밖으로 밀려난 것이 있는가 — 화면이 「대화가 길어졌다」를 알릴 근거다.\n *\n * 이 문은 그 사실을 내지 않았다. 대화창에는 그 안내를 그리는 자리가 있고(`historyFolded`), 보드\n * 문만 그 값을 채웠다. 그래서 앱의 대화는 앞부분이 잘려 나가도 **사람에게는 아무 말도 하지 않았다** —\n * 모델에게는 「N 개 생략」을 적어 보내면서.\n */\n const readRows = session\n ? await messageRepo.find({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'ASC' },\n relations: ['creator']\n })\n : []\n /*\n * 모델이 읽는 이력도 **부른 사람** 기준으로 가린다 — ADR-0073 판정 1. 가리지 않으면, 문을 한 번도\n * 안 지난 사람이 \"아까 그 수 다시 말해 줘\" 한 마디로 값을 가져간다. 모델이 옮겨 적는 순간 그 값은\n * 문 없는 새 답이 된다. 가린 자리를 지우지 않고 한 줄로 남기는 이유도 같다 — 지우면 모델이 그런\n * 말이 없었다고 답하고, 그것은 거짓말이다.\n */\n const storedRows = await readerGate(context)(readRows)\n const capped = splitByCap(\n storedRows.map(row => ({ id: row.id, role: row.role, content: row.content ?? '' })),\n LLM_HISTORY_MAX_TURNS\n )\n const historyFolded =\n capped.dropped.length > 0\n ? { omitted: capped.dropped.length, summarized: Boolean((session as any)?.lastSummary) }\n : undefined\n\n const conversation = session\n ? buildLlmHistory(\n /*\n * The model reads who spoke and who they spoke to. The entity carries the speaker as a\n * relation and the addressee as a column; the history wants them as plain fields, and\n * without this mapping every line arrived unattributed — a room of people read as one\n * voice (ADR-0070 A15 decision ②).\n */\n storedRows.map(message => ({\n id: message.id,\n role: message.role,\n /*\n * A file sent in an earlier turn is not sent again - one line names it, and the model opens it\n * with readChatAttachment when it needs to (cost is a usage axis, ADR-0061 decision 6).\n */\n content:\n message.id !== userMessageId && (message as any).attachmentsJson?.length\n ? [message.content, ...(message as any).attachmentsJson.map(attachmentHistoryLine)].join('\\n')\n : message.content,\n senderId: (message as any).creator?.id,\n senderName: (message as any).creator?.name,\n addressee: (message as any).addressee\n })),\n {\n maxTurns: LLM_HISTORY_MAX_TURNS,\n truncateAfterMessageId: input.truncateAfterMessageId,\n /* The message just stored is this turn's question — truncation must never drop it. */\n keepMessageIds: userMessageId ? [userMessageId] : undefined,\n summary: (session as any).lastSummary\n ? { text: (session as any).lastSummary, upToMessageId: (session as any).summaryUpToMessageId }\n : undefined\n }\n )\n : input.messages.map(message => ({ role: message.role as any, content: message.content }))\n\n if (attachmentRows.length && conversation.length > 0) {\n const last = conversation[conversation.length - 1]\n if (last.role === 'user' && typeof last.content === 'string') {\n const parts: any[] = [{ type: 'text', text: last.content }]\n for (const row of attachmentRows) {\n const file = await attachmentForModel(row, textCharsOf(getAssistantMode(input.mode?.kind ?? '')?.attachments))\n if (file.type === 'image') {\n parts.push({ type: 'image', source: { kind: 'base64', data: file.data, mediaType: file.mediaType } })\n } else {\n parts.push({\n type: 'text',\n text: `[attachment ${row.id}: ${file.name}${file.truncated ? ' - cut, the file is longer' : ''}]\\n${file.text}`\n })\n }\n }\n last.content = parts as any\n }\n } else if (input.hostContext?.image?.data && conversation.length > 0) {\n /* The screen-image path (figure's rendered view) - not a file of the conversation. */\n const last = conversation[conversation.length - 1]\n if (last.role === 'user' && typeof last.content === 'string') {\n const raw = input.hostContext.image\n const rawData = typeof raw.data === 'string' ? raw.data.replace(/^data:image\\/[a-zA-Z]+;base64,/, '') : raw.data\n const buf = typeof rawData === 'string' ? Buffer.from(rawData, 'base64') : Buffer.from(rawData)\n last.content = [\n { type: 'text', text: last.content },\n { type: 'image', source: { kind: 'base64', data: buf, mediaType: raw.mediaType || 'image/png' } }\n ] as any\n }\n }\n\n const result = await registryAssistantChat({\n client,\n messages: conversation as any,\n systemPrompt,\n toolCategories: settings.toolCategories,\n /* 도구가 「어느 대화에서 불렸나」를 안다 — 서버의 값이다(ADR-0061 결정 3). */\n mode: { kind: input.mode.kind, ...(input.mode.target ? { target: input.mode.target } : {}) },\n toolAccess: toolAccessFor(context),\n /* The stop notice is a sentence a person reads — the server writes it in their language (ADR-0054). */\n t,\n hostContext: input.hostContext,\n /*\n * 근거는 도구 결과만이 아니다 — 시스템 프롬프트 · 이 대화의 말 · 화면이 보낸 스냅숏도 근거다\n * (`ai-client-base/utils/grounding`). 이 문은 그 셋을 넘기지 않아, **사람이 방금 타이핑한\n * 식별자**를 되받아 적은 답이 「근거 없음」으로 표시됐다. 그런 경고가 몇 번 나오면 사람이 경고\n * 자체를 안 보게 된다.\n */\n groundingSources: groundingSourcesFor({\n systemPrompt,\n messages: conversation as any,\n hostContext: input.hostContext\n }),\n state: context.state,\n temperature: settings.temperature,\n maxTokens: settings.maxTokens,\n requireToolOnFirstTurn: settings.requireToolOnFirstTurn\n })\n\n /*\n * 이 답이 지나온 문 — ADR-0073 판정 1. 행에도, 목록 미리보기에도 같은 값이 간다. 한쪽만 적으면\n * 가려진 답이 목록에는 그대로 뜬다.\n */\n /* The reply ends with the next request when one is natural; the input offers it and the stored answer does not keep it (§4-1). */\n const { reply, suggestion } = splitNextRequest(result.reply)\n\n const answerDoors = doorsForAnswer(\n (result.loop.toolUsages ?? []).map((usage: any) => usage?.name).filter(Boolean),\n name => (findToolSpec(name, settings.toolCategories) as any)?.doors\n )\n\n let assistantMessageId: string | undefined\n if (session) {\n const stored = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'assistant',\n content: reply,\n /*\n * 이 답이 지나온 문을 함께 적는다 — ADR-0073 판정 1. 방은 도메인 안에서 공개이므로, 이 문장이\n * 실어 나르는 값을 나중에 읽는 사람이 열 수 있는지 판정할 근거가 행에 있어야 한다. 적지 않으면\n * 빈 목록이 되고, 빈 목록은 누구나 읽는다는 뜻이다.\n */\n doors: answerDoors,\n toolUsages: result.loop.toolUsages?.length ? JSON.stringify(result.loop.toolUsages) : undefined,\n proposals: result.loop.proposals?.length ? JSON.stringify(result.loop.proposals) : undefined,\n groundingWarnings: result.groundingWarnings?.length ? JSON.stringify(result.groundingWarnings) : undefined,\n creator: user,\n updater: user,\n parentMessage: userMessageId ? ({ id: userMessageId } as any) : undefined\n })\n assistantMessageId = stored.id\n\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: stored } as any)\n /* The reply is session activity too. Leaving it out shows other participants \"nothing new\". */\n publishSessionActivity({\n domainId: domain.id,\n sessionId: session.id!,\n kind: 'message',\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n preview: reply,\n actorId: user?.id\n })\n\n await sessionRepo.update(session.id!, {\n aiClientId: client.id,\n agentId: client.agent.id,\n updater: user,\n ...activityPatch(reply, new Date(), answerDoors)\n } as any)\n\n /*\n * 상한 밖으로 밀려난 앞부분을 요약으로 접는다 — **답을 돌려준 뒤**에 한다(사용자 대기 시간에\n * 요약 호출을 얹지 않는다). 기다리지 않으므로 실패해도 대화에는 영향이 없다: 요약이 없으면 다음\n * 턴이 「N 개 생략」으로 정직하게 알린다.\n *\n * 이 문에는 이 호출이 없었다. 조각은 다 있었는데(상한 · 생략 알림 · `lastSummary` · 순수 규칙)\n * 부르는 쪽이 보드 제품 하나뿐이어서, 앱의 대화는 며칠이 지나도 앞부분을 요약하지 않고 매 턴\n * 「생략했다」고만 말했다 — 협의가 길어질수록 처음에 합의한 것이 먼저 사라진다.\n */\n void foldSessionHistory({\n sessionId: session.id!,\n domainId: domain.id,\n client,\n maxTurns: LLM_HISTORY_MAX_TURNS,\n locale: context.state?.lng\n })\n }\n\n return {\n reply,\n suggestion: suggestion ?? null,\n clientId: client.id,\n agentId: client.agent.id,\n sessionId: session?.id,\n userMessageId,\n assistantMessageId,\n toolUsages: result.loop.toolUsages?.length ? result.loop.toolUsages : null,\n offeredTools: result.offeredTools,\n proposals: result.loop.proposals?.length ? result.loop.proposals : null,\n groundingWarnings: result.groundingWarnings?.length ? result.groundingWarnings : null,\n historyFolded: historyFolded ?? null\n }\n }\n}\n"]}
@@ -25,10 +25,9 @@ declare class AssistantModeView {
25
25
  declare class AssistantModeChoice {
26
26
  kind: string;
27
27
  label: string;
28
- isDefault: boolean;
29
28
  }
30
29
  export declare class AssistantModeResolver {
31
- assistantModes(subject: string, context: any): Promise<AssistantModeChoice[]>;
30
+ assistantModeFor(subject: string, context: any): Promise<AssistantModeChoice | null>;
32
31
  assistantMode(kind: string, context: any): Promise<AssistantModeView>;
33
32
  }
34
33
  export {};
@@ -89,36 +89,24 @@ tslib_1.__decorate([
89
89
  tslib_1.__metadata("design:type", String)
90
90
  ], AssistantModeChoice.prototype, "kind", void 0);
91
91
  tslib_1.__decorate([
92
- (0, type_graphql_1.Field)({ description: 'What to call it where a person chooses — already in the words of this request.' }),
92
+ (0, type_graphql_1.Field)({ description: 'What the conversation is called in the conversation list, in the words of this request.' }),
93
93
  tslib_1.__metadata("design:type", String)
94
94
  ], AssistantModeChoice.prototype, "label", void 0);
95
- tslib_1.__decorate([
96
- (0, type_graphql_1.Field)({ description: 'Whether this is the one to open when the person has not chosen.' }),
97
- tslib_1.__metadata("design:type", Boolean)
98
- ], AssistantModeChoice.prototype, "isDefault", void 0);
99
95
  AssistantModeChoice = tslib_1.__decorate([
100
- (0, type_graphql_1.ObjectType)({ description: 'A conversation a screen may open for what it is showing.' })
96
+ (0, type_graphql_1.ObjectType)({ description: 'The conversation a screen opens for what it is showing.' })
101
97
  ], AssistantModeChoice);
102
98
  let AssistantModeResolver = class AssistantModeResolver {
103
99
  /*
104
- * 이 화면이 보여 주는 것으로 열 수 있는 대화들 — ADR-0072 A17 ①.
105
- *
106
- * 화면은 자기가 무엇을 보여 주는지(`subject`)만 안다. 그 위에 어느 대화가 서는지는 앱 서버가
107
- * 선언한다. 화면이 종류 목록을 손으로 들고 있으면, 대화를 하나 더 세운 날 그 화면만 모른 채로 남는다.
100
+ * 이 화면의 대화 — 화면에 대화 하나(design/ai/screen-pattern.md §3).
108
101
  *
109
- * 하나만 오면 고르개를 내지 않는다 — 고를 것이 없는 고르개는 자리만 차지한다.
102
+ * 화면은 자기가 무엇을 보여 주는지(`subject`)만 안다. 그 위에 어느 대화가 서는지는 앱 서버가 선언한다.
110
103
  */
111
- async assistantModes(subject, context) {
104
+ async assistantModeFor(subject, context) {
105
+ const mode = (0, registry_js_1.assistantModeForSubject)(subject);
106
+ if (!mode)
107
+ return null;
112
108
  const t = (0, request_translator_js_1.requestTranslator)(context);
113
- return (0, registry_js_1.assistantModesForSubject)(subject).map(mode => ({
114
- kind: mode.kind,
115
- /*
116
- * 이름이 없는 대화는 종류를 그대로 쓴다. 부팅 보고가 이미 그것을 경고로 말하므로 여기서 숨기지
117
- * 않는다 — 화면에 `twin.space` 가 뜨면 그것이 빠진 선언을 가리킨다.
118
- */
119
- label: mode.labelKey ? t(mode.labelKey) : mode.kind,
120
- isDefault: (mode.defaultFor ?? []).includes(subject)
121
- }));
109
+ return { kind: mode.kind, label: mode.labelKey ? t(mode.labelKey) : mode.kind };
122
110
  }
123
111
  async assistantMode(kind, context) {
124
112
  return (0, resolve_js_1.assistantModeView)(kind, (0, request_translator_js_1.requestTranslator)(context));
@@ -126,8 +114,9 @@ let AssistantModeResolver = class AssistantModeResolver {
126
114
  };
127
115
  exports.AssistantModeResolver = AssistantModeResolver;
128
116
  tslib_1.__decorate([
129
- (0, type_graphql_1.Query)(() => [AssistantModeChoice], {
130
- description: 'Conversations that stand for this kind of subject, the default first. A screen showing one subject opens the head of this list and offers the rest; a single entry means there is nothing to choose.'
117
+ (0, type_graphql_1.Query)(() => AssistantModeChoice, {
118
+ nullable: true,
119
+ description: 'The one conversation for this kind of subject. Null when no application declared one.'
131
120
  }),
132
121
  (0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query", domainOwnerGranted: true)'),
133
122
  tslib_1.__param(0, (0, type_graphql_1.Arg)('subject')),
@@ -135,7 +124,7 @@ tslib_1.__decorate([
135
124
  tslib_1.__metadata("design:type", Function),
136
125
  tslib_1.__metadata("design:paramtypes", [String, Object]),
137
126
  tslib_1.__metadata("design:returntype", Promise)
138
- ], AssistantModeResolver.prototype, "assistantModes", null);
127
+ ], AssistantModeResolver.prototype, "assistantModeFor", null);
139
128
  tslib_1.__decorate([
140
129
  (0, type_graphql_1.Query)(() => AssistantModeView, {
141
130
  description: 'What this conversation offers on screen — `/` sentences, the `@` catalog and the notices, translated.'