@things-factory/ai-assistant 10.1.157 → 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 +6 -6
  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
@@ -1 +1 @@
1
- {"version":3,"file":"assistant-mode-resolver.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/assistant-mode-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The screen asks what its conversation offers.\n *\n * The `/` sentences, the `@` catalog and the two notices used to travel from the browser into the\n * request, which meant the screen and the door could describe the same conversation differently.\n * They are declared once, in the mode, as i18n keys — and this query hands them over already\n * written in the request's locale (ADR-0054, and the same rule A11 applied to the stop notice).\n * The persona, the tool categories and the limits stay on the server: nothing here returns them.\n */\nimport { Arg, Ctx, Directive, Field, ObjectType, Query, Resolver } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\nimport '@things-factory/auth-base'\n\nimport { assistantModeView } from './resolve.js'\nimport { assistantModesForSubject } from './registry.js'\nimport { requestTranslator } from '../request-translator.js'\n\n@ObjectType({ description: 'One `/` sentence, in the words of this request.' })\nclass AssistantModeSlashView {\n @Field({ description: 'Short name shown in the list.' })\n name: string\n\n @Field({ description: 'One line about what it does.' })\n description: string\n\n @Field({ description: 'The sentence that lands in the input.' })\n template: string\n}\n\n@ObjectType({ description: 'What a conversation offers on screen.' })\nclass AssistantModeView {\n @Field({ description: 'The mode kind this describes.' })\n kind: string\n\n @Field(() => [AssistantModeSlashView], { description: 'What `/` offers.' })\n slashTemplates: AssistantModeSlashView[]\n\n @Field(() => GraphQLJSON, { description: 'What `@` offers.' })\n catalogEntries: any[]\n\n @Field(() => GraphQLJSON, { description: 'Follow-up chips for a proposal that arrived without its own.' })\n proposalChoices: any[]\n\n @Field({ nullable: true, description: 'What the empty input says — what this conversation is for.' })\n placeholder?: string\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'Title and one line for an empty conversation.' })\n intro?: { title: string; subtitle: string }\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'Example sentences this conversation can actually answer.' })\n examples?: Array<{ label: string; items: string[] }>\n\n @Field({ nullable: true, description: 'The line under an empty conversation.' })\n footerNotice?: string\n\n @Field({ nullable: true, description: 'The line shown once the host reports a proposal staged.' })\n stagedNotice?: string\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description: \"What this conversation accepts as attachments: { kinds: ('image'|'text'|'spreadsheet')[], maxBytes, maxFiles, maxChars }. Null: none.\"\n })\n attachments?: any\n}\n\n@ObjectType({ description: 'A conversation a screen may open for what it is showing.' })\nclass AssistantModeChoice {\n @Field({ description: 'The mode kind to open.' })\n kind: string\n\n @Field({ description: 'What to call it where a person chooses — already in the words of this request.' })\n label: string\n\n @Field({ description: 'Whether this is the one to open when the person has not chosen.' })\n isDefault: boolean\n}\n\n@Resolver()\nexport class AssistantModeResolver {\n /*\n * 이 화면이 보여 주는 것으로 열 수 있는 대화들 — ADR-0072 A17 ①.\n *\n * 화면은 자기가 무엇을 보여 주는지(`subject`)만 안다. 그 위에 어느 대화가 서는지는 앱 서버가\n * 선언한다. 화면이 종류 목록을 손으로 들고 있으면, 대화를 하나 더 세운 날 그 화면만 모른 채로 남는다.\n *\n * 하나만 오면 고르개를 내지 않는다 — 고를 것이 없는 고르개는 자리만 차지한다.\n */\n @Query(() => [AssistantModeChoice], {\n description:\n '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.'\n })\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"query\", domainOwnerGranted: true)')\n async assistantModes(@Arg('subject') subject: string, @Ctx() context: any): Promise<AssistantModeChoice[]> {\n const t = requestTranslator(context)\n\n return assistantModesForSubject(subject).map(mode => ({\n kind: mode.kind,\n /*\n * 이름이 없는 대화는 종류를 그대로 쓴다. 부팅 보고가 이미 그것을 경고로 말하므로 여기서 숨기지\n * 않는다 — 화면에 `twin.space` 가 뜨면 그것이 빠진 선언을 가리킨다.\n */\n label: mode.labelKey ? t(mode.labelKey) : mode.kind,\n isDefault: (mode.defaultFor ?? []).includes(subject)\n }))\n }\n @Query(() => AssistantModeView, {\n description: 'What this conversation offers on screen — `/` sentences, the `@` catalog and the notices, translated.'\n })\n /* Reading what a conversation offers is reading, and the same door the session list uses. */\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"query\", domainOwnerGranted: true)')\n async assistantMode(@Arg('kind') kind: string, @Ctx() context: any): Promise<AssistantModeView> {\n return assistantModeView(kind, requestTranslator(context)) as AssistantModeView\n }\n}\n"]}
1
+ {"version":3,"file":"assistant-mode-resolver.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/assistant-mode-resolver.ts"],"names":[],"mappings":";;;;AAAA;;;;;;;;GAQG;AACH,+CAAsF;AACtF,qDAA6C;AAC7C,qCAAkC;AAElC,6CAAgD;AAChD,+CAAuD;AACvD,oEAA4D;AAG5D,IAAM,sBAAsB,GAA5B,MAAM,sBAAsB;CAS3B,CAAA;AAPC;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,+BAA+B,EAAE,CAAC;;oDAC5C;AAGZ;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,8BAA8B,EAAE,CAAC;;2DACpC;AAGnB;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,uCAAuC,EAAE,CAAC;;wDAChD;AARZ,sBAAsB;IAD3B,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,iDAAiD,EAAE,CAAC;GACzE,sBAAsB,CAS3B;AAGD,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;CAiCtB,CAAA;AA/BC;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,+BAA+B,EAAE,CAAC;;+CAC5C;AAGZ;IADC,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,CAAC,sBAAsB,CAAC,EAAE,EAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;;yDACnC;AAGxC;IADC,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,6BAAW,EAAE,EAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;;yDACzC;AAGrB;IADC,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,6BAAW,EAAE,EAAE,WAAW,EAAE,8DAA8D,EAAE,CAAC;;0DACpF;AAGtB;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,4DAA4D,EAAE,CAAC;;sDACjF;AAGpB;IADC,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,6BAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,+CAA+C,EAAE,CAAC;;gDAChE;AAG3C;IADC,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,6BAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,0DAA0D,EAAE,CAAC;sCAC3G,KAAK;mDAAoC;AAGpD;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uCAAuC,EAAE,CAAC;;uDAC3D;AAGrB;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,yDAAyD,EAAE,CAAC;;uDAC7E;AAMrB;IAJC,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,6BAAW,EAAE;QACxB,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,uIAAuI;KACrJ,CAAC;;sDACe;AAhCb,iBAAiB;IADtB,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,uCAAuC,EAAE,CAAC;GAC/D,iBAAiB,CAiCtB;AAGD,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;CAMxB,CAAA;AAJC;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,wBAAwB,EAAE,CAAC;;iDACrC;AAGZ;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,yFAAyF,EAAE,CAAC;;kDACrG;AALT,mBAAmB;IADxB,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,yDAAyD,EAAE,CAAC;GACjF,mBAAmB,CAMxB;AAGM,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAChC;;;;OAIG;IAMG,AAAN,KAAK,CAAC,gBAAgB,CAAiB,OAAe,EAAS,OAAY;QACzE,MAAM,IAAI,GAAG,IAAA,qCAAuB,EAAC,OAAO,CAAC,CAAA;QAC7C,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA;QACtB,MAAM,CAAC,GAAG,IAAA,yCAAiB,EAAC,OAAO,CAAC,CAAA;QACpC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;IACjF,CAAC;IAMK,AAAN,KAAK,CAAC,aAAa,CAAc,IAAY,EAAS,OAAY;QAChE,OAAO,IAAA,8BAAiB,EAAC,IAAI,EAAE,IAAA,yCAAiB,EAAC,OAAO,CAAC,CAAsB,CAAA;IACjF,CAAC;CACF,CAAA;AAzBY,sDAAqB;AAW1B;IALL,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,mBAAmB,EAAE;QAChC,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,uFAAuF;KACrG,CAAC;IACD,IAAA,wBAAS,EAAC,oFAAoF,CAAC;IACxE,mBAAA,IAAA,kBAAG,EAAC,SAAS,CAAC,CAAA;IAAmB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;6DAK7D;AAMK;IALL,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,iBAAiB,EAAE;QAC9B,WAAW,EAAE,uGAAuG;KACrH,CAAC;IACF,6FAA6F;;IAC5F,IAAA,wBAAS,EAAC,oFAAoF,CAAC;IAC3E,mBAAA,IAAA,kBAAG,EAAC,MAAM,CAAC,CAAA;IAAgB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;0DAEpD;gCAxBU,qBAAqB;IADjC,IAAA,uBAAQ,GAAE;GACE,qBAAqB,CAyBjC","sourcesContent":["/*\n * The screen asks what its conversation offers.\n *\n * The `/` sentences, the `@` catalog and the two notices used to travel from the browser into the\n * request, which meant the screen and the door could describe the same conversation differently.\n * They are declared once, in the mode, as i18n keys — and this query hands them over already\n * written in the request's locale (ADR-0054, and the same rule A11 applied to the stop notice).\n * The persona, the tool categories and the limits stay on the server: nothing here returns them.\n */\nimport { Arg, Ctx, Directive, Field, ObjectType, Query, Resolver } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\nimport '@things-factory/auth-base'\n\nimport { assistantModeView } from './resolve.js'\nimport { assistantModeForSubject } from './registry.js'\nimport { requestTranslator } from '../request-translator.js'\n\n@ObjectType({ description: 'One `/` sentence, in the words of this request.' })\nclass AssistantModeSlashView {\n @Field({ description: 'Short name shown in the list.' })\n name: string\n\n @Field({ description: 'One line about what it does.' })\n description: string\n\n @Field({ description: 'The sentence that lands in the input.' })\n template: string\n}\n\n@ObjectType({ description: 'What a conversation offers on screen.' })\nclass AssistantModeView {\n @Field({ description: 'The mode kind this describes.' })\n kind: string\n\n @Field(() => [AssistantModeSlashView], { description: 'What `/` offers.' })\n slashTemplates: AssistantModeSlashView[]\n\n @Field(() => GraphQLJSON, { description: 'What `@` offers.' })\n catalogEntries: any[]\n\n @Field(() => GraphQLJSON, { description: 'Follow-up chips for a proposal that arrived without its own.' })\n proposalChoices: any[]\n\n @Field({ nullable: true, description: 'What the empty input says — what this conversation is for.' })\n placeholder?: string\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'Title and one line for an empty conversation.' })\n intro?: { title: string; subtitle: string }\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'Example sentences this conversation can actually answer.' })\n examples?: Array<{ label: string; items: string[] }>\n\n @Field({ nullable: true, description: 'The line under an empty conversation.' })\n footerNotice?: string\n\n @Field({ nullable: true, description: 'The line shown once the host reports a proposal staged.' })\n stagedNotice?: string\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description: \"What this conversation accepts as attachments: { kinds: ('image'|'text'|'spreadsheet')[], maxBytes, maxFiles, maxChars }. Null: none.\"\n })\n attachments?: any\n}\n\n@ObjectType({ description: 'The conversation a screen opens for what it is showing.' })\nclass AssistantModeChoice {\n @Field({ description: 'The mode kind to open.' })\n kind: string\n\n @Field({ description: 'What the conversation is called in the conversation list, in the words of this request.' })\n label: string\n}\n\n@Resolver()\nexport class AssistantModeResolver {\n /*\n * 이 화면의 대화 — 화면에 대화 하나(design/ai/screen-pattern.md §3).\n *\n * 화면은 자기가 무엇을 보여 주는지(`subject`)만 안다. 그 위에 어느 대화가 서는지는 앱 서버가 선언한다.\n */\n @Query(() => AssistantModeChoice, {\n nullable: true,\n description: 'The one conversation for this kind of subject. Null when no application declared one.'\n })\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"query\", domainOwnerGranted: true)')\n async assistantModeFor(@Arg('subject') subject: string, @Ctx() context: any): Promise<AssistantModeChoice | null> {\n const mode = assistantModeForSubject(subject)\n if (!mode) return null\n const t = requestTranslator(context)\n return { kind: mode.kind, label: mode.labelKey ? t(mode.labelKey) : mode.kind }\n }\n @Query(() => AssistantModeView, {\n description: 'What this conversation offers on screen — `/` sentences, the `@` catalog and the notices, translated.'\n })\n /* Reading what a conversation offers is reading, and the same door the session list uses. */\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"query\", domainOwnerGranted: true)')\n async assistantMode(@Arg('kind') kind: string, @Ctx() context: any): Promise<AssistantModeView> {\n return assistantModeView(kind, requestTranslator(context)) as AssistantModeView\n }\n}\n"]}
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * The open subjects the shared parts can name without an application's help (design/ai/screen-pattern.md §2-1):
5
+ * the site (`domain`), an application's home (`application`) and a board (`board`). An application that names
6
+ * one of these differently registers its own describer, which is read first.
7
+ *
8
+ * A board is read by entity name: the board entity belongs to board-service, and this package does not depend on
9
+ * it. A deployment without boards has no board anchors, and a read that fails says the subject could not be read.
10
+ */
11
+ const shell_1 = require("@things-factory/shell");
12
+ const ai_client_base_1 = require("@things-factory/ai-client-base");
13
+ (0, ai_client_base_1.registerFallbackSubject)({
14
+ anchorType: 'domain',
15
+ async describe({ anchorId, domain }) {
16
+ /* The site of this conversation only - another domain's name is not this person's to read. */
17
+ if (anchorId !== domain.id)
18
+ return undefined;
19
+ const found = await (0, shell_1.getRepository)(shell_1.Domain).findOne({ where: { id: anchorId } });
20
+ return found ? { noun: 'site', name: found.description || found.name } : undefined;
21
+ }
22
+ });
23
+ (0, ai_client_base_1.registerFallbackSubject)({
24
+ anchorType: 'application',
25
+ async describe({ anchorId }) {
26
+ /* The application's home is a place, not a record: nothing narrower is open. */
27
+ return { noun: 'home of the application', name: anchorId };
28
+ }
29
+ });
30
+ (0, ai_client_base_1.registerFallbackSubject)({
31
+ anchorType: 'board',
32
+ async describe({ anchorId, domain }) {
33
+ const found = await (0, shell_1.getRepository)('Board').findOne({ where: { id: anchorId, domain: { id: domain.id } } });
34
+ return found?.name ? { noun: 'board', name: found.name } : undefined;
35
+ }
36
+ });
37
+ //# sourceMappingURL=fallback-subjects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fallback-subjects.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/fallback-subjects.ts"],"names":[],"mappings":";;AAAA;;;;;;;GAOG;AACH,iDAA6D;AAC7D,mEAAwE;AAExE,IAAA,wCAAuB,EAAC;IACtB,UAAU,EAAE,QAAQ;IACpB,KAAK,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE;QACjC,8FAA8F;QAC9F,IAAI,QAAQ,KAAK,MAAM,CAAC,EAAE;YAAE,OAAO,SAAS,CAAA;QAC5C,MAAM,KAAK,GAAG,MAAM,IAAA,qBAAa,EAAC,cAAM,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAA;QAC9E,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IACpF,CAAC;CACF,CAAC,CAAA;AAEF,IAAA,wCAAuB,EAAC;IACtB,UAAU,EAAE,aAAa;IACzB,KAAK,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE;QACzB,gFAAgF;QAChF,OAAO,EAAE,IAAI,EAAE,yBAAyB,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;IAC5D,CAAC;CACF,CAAC,CAAA;AAEF,IAAA,wCAAuB,EAAC;IACtB,UAAU,EAAE,OAAO;IACnB,KAAK,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE;QACjC,MAAM,KAAK,GAAQ,MAAM,IAAA,qBAAa,EAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;QAC/G,OAAO,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IACtE,CAAC;CACF,CAAC,CAAA","sourcesContent":["/**\n * The open subjects the shared parts can name without an application's help (design/ai/screen-pattern.md §2-1):\n * the site (`domain`), an application's home (`application`) and a board (`board`). An application that names\n * one of these differently registers its own describer, which is read first.\n *\n * A board is read by entity name: the board entity belongs to board-service, and this package does not depend on\n * it. A deployment without boards has no board anchors, and a read that fails says the subject could not be read.\n */\nimport { Domain, getRepository } from '@things-factory/shell'\nimport { registerFallbackSubject } from '@things-factory/ai-client-base'\n\nregisterFallbackSubject({\n anchorType: 'domain',\n async describe({ anchorId, domain }) {\n /* The site of this conversation only - another domain's name is not this person's to read. */\n if (anchorId !== domain.id) return undefined\n const found = await getRepository(Domain).findOne({ where: { id: anchorId } })\n return found ? { noun: 'site', name: found.description || found.name } : undefined\n }\n})\n\nregisterFallbackSubject({\n anchorType: 'application',\n async describe({ anchorId }) {\n /* The application's home is a place, not a record: nothing narrower is open. */\n return { noun: 'home of the application', name: anchorId }\n }\n})\n\nregisterFallbackSubject({\n anchorType: 'board',\n async describe({ anchorId, domain }) {\n const found: any = await getRepository('Board').findOne({ where: { id: anchorId, domain: { id: domain.id } } })\n return found?.name ? { noun: 'board', name: found.name } : undefined\n }\n})\n"]}
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Design: ADR-0061 decision 7 (`operato-application/adr/ADR-0061.md`) and the AI standard's A2.
5
5
  */
6
- export { registerAssistantMode, getAssistantMode, listAssistantModes, assistantModesForSubject, getAssistantModeWarnings, clearAssistantModes } from './registry.js';
6
+ export { registerAssistantMode, getAssistantMode, listAssistantModes, assistantModeForSubject, getAssistantModeWarnings, clearAssistantModes } from './registry.js';
7
7
  export { resolveModeSettings, assistantModeView } from './resolve.js';
8
+ export { ABSENCE_RULE, NEXT_REQUEST_RULE, conversationPrompt, splitNextRequest } from './screen-rules.js';
8
9
  export type { AssistantModeSpec, AssistantModeSlash, AssistantModeWarning } from './types.js';
9
10
  export type { AssistantModeRef, ModeSettings, AssistantModeView, AssistantModeSlashView, Translate } from './resolve.js';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.assistantModeView = exports.resolveModeSettings = exports.clearAssistantModes = exports.getAssistantModeWarnings = exports.assistantModesForSubject = exports.listAssistantModes = exports.getAssistantMode = exports.registerAssistantMode = void 0;
3
+ exports.splitNextRequest = exports.conversationPrompt = exports.NEXT_REQUEST_RULE = exports.ABSENCE_RULE = exports.assistantModeView = exports.resolveModeSettings = exports.clearAssistantModes = exports.getAssistantModeWarnings = exports.assistantModeForSubject = exports.listAssistantModes = exports.getAssistantMode = exports.registerAssistantMode = void 0;
4
4
  /**
5
5
  * Modes — what each conversation is, declared by the app server that owns the screen.
6
6
  *
@@ -10,10 +10,15 @@ var registry_js_1 = require("./registry.js");
10
10
  Object.defineProperty(exports, "registerAssistantMode", { enumerable: true, get: function () { return registry_js_1.registerAssistantMode; } });
11
11
  Object.defineProperty(exports, "getAssistantMode", { enumerable: true, get: function () { return registry_js_1.getAssistantMode; } });
12
12
  Object.defineProperty(exports, "listAssistantModes", { enumerable: true, get: function () { return registry_js_1.listAssistantModes; } });
13
- Object.defineProperty(exports, "assistantModesForSubject", { enumerable: true, get: function () { return registry_js_1.assistantModesForSubject; } });
13
+ Object.defineProperty(exports, "assistantModeForSubject", { enumerable: true, get: function () { return registry_js_1.assistantModeForSubject; } });
14
14
  Object.defineProperty(exports, "getAssistantModeWarnings", { enumerable: true, get: function () { return registry_js_1.getAssistantModeWarnings; } });
15
15
  Object.defineProperty(exports, "clearAssistantModes", { enumerable: true, get: function () { return registry_js_1.clearAssistantModes; } });
16
16
  var resolve_js_1 = require("./resolve.js");
17
17
  Object.defineProperty(exports, "resolveModeSettings", { enumerable: true, get: function () { return resolve_js_1.resolveModeSettings; } });
18
18
  Object.defineProperty(exports, "assistantModeView", { enumerable: true, get: function () { return resolve_js_1.assistantModeView; } });
19
+ var screen_rules_js_1 = require("./screen-rules.js");
20
+ Object.defineProperty(exports, "ABSENCE_RULE", { enumerable: true, get: function () { return screen_rules_js_1.ABSENCE_RULE; } });
21
+ Object.defineProperty(exports, "NEXT_REQUEST_RULE", { enumerable: true, get: function () { return screen_rules_js_1.NEXT_REQUEST_RULE; } });
22
+ Object.defineProperty(exports, "conversationPrompt", { enumerable: true, get: function () { return screen_rules_js_1.conversationPrompt; } });
23
+ Object.defineProperty(exports, "splitNextRequest", { enumerable: true, get: function () { return screen_rules_js_1.splitNextRequest; } });
19
24
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/index.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,6CAOsB;AANpB,oHAAA,qBAAqB,OAAA;AACrB,+GAAA,gBAAgB,OAAA;AAChB,iHAAA,kBAAkB,OAAA;AAClB,uHAAA,wBAAwB,OAAA;AACxB,uHAAA,wBAAwB,OAAA;AACxB,kHAAA,mBAAmB,OAAA;AAErB,2CAAqE;AAA5D,iHAAA,mBAAmB,OAAA;AAAE,+GAAA,iBAAiB,OAAA","sourcesContent":["/**\n * Modes — what each conversation is, declared by the app server that owns the screen.\n *\n * Design: ADR-0061 decision 7 (`operato-application/adr/ADR-0061.md`) and the AI standard's A2.\n */\nexport {\n registerAssistantMode,\n getAssistantMode,\n listAssistantModes,\n assistantModesForSubject,\n getAssistantModeWarnings,\n clearAssistantModes\n} from './registry.js'\nexport { resolveModeSettings, assistantModeView } from './resolve.js'\nexport type { AssistantModeSpec, AssistantModeSlash, AssistantModeWarning } from './types.js'\nexport type { AssistantModeRef, ModeSettings, AssistantModeView, AssistantModeSlashView, Translate } from './resolve.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/index.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,6CAOsB;AANpB,oHAAA,qBAAqB,OAAA;AACrB,+GAAA,gBAAgB,OAAA;AAChB,iHAAA,kBAAkB,OAAA;AAClB,sHAAA,uBAAuB,OAAA;AACvB,uHAAA,wBAAwB,OAAA;AACxB,kHAAA,mBAAmB,OAAA;AAErB,2CAAqE;AAA5D,iHAAA,mBAAmB,OAAA;AAAE,+GAAA,iBAAiB,OAAA;AAC/C,qDAAyG;AAAhG,+GAAA,YAAY,OAAA;AAAE,oHAAA,iBAAiB,OAAA;AAAE,qHAAA,kBAAkB,OAAA;AAAE,mHAAA,gBAAgB,OAAA","sourcesContent":["/**\n * Modes — what each conversation is, declared by the app server that owns the screen.\n *\n * Design: ADR-0061 decision 7 (`operato-application/adr/ADR-0061.md`) and the AI standard's A2.\n */\nexport {\n registerAssistantMode,\n getAssistantMode,\n listAssistantModes,\n assistantModeForSubject,\n getAssistantModeWarnings,\n clearAssistantModes\n} from './registry.js'\nexport { resolveModeSettings, assistantModeView } from './resolve.js'\nexport { ABSENCE_RULE, NEXT_REQUEST_RULE, conversationPrompt, splitNextRequest } from './screen-rules.js'\nexport type { AssistantModeSpec, AssistantModeSlash, AssistantModeWarning } from './types.js'\nexport type { AssistantModeRef, ModeSettings, AssistantModeView, AssistantModeSlashView, Translate } from './resolve.js'\n"]}
@@ -8,11 +8,7 @@ export declare function listAssistantModes(): ReadonlyArray<AssistantModeSpec>;
8
8
  * Read after boot: a mode may register before the category its slash sentences use.
9
9
  */
10
10
  export declare function getAssistantModeWarnings(): ReadonlyArray<AssistantModeWarning>;
11
- /**
12
- * 이 subject 종류에 서는 대화들 — 등록 순서대로, 기본이 먼저.
13
- *
14
- * 화면은 이 목록으로 고르개를 낸다. 하나뿐이면 고르개를 내지 않는다(ADR-0072 A17 ①).
15
- */
16
- export declare function assistantModesForSubject(subject: string): ReadonlyArray<AssistantModeSpec>;
11
+ /** The conversation a screen showing this kind of subject opens — one per subject (design/ai/screen-pattern.md §3). */
12
+ export declare function assistantModeForSubject(subject: string): AssistantModeSpec | undefined;
17
13
  /** Test only. */
18
14
  export declare function clearAssistantModes(): void;
@@ -4,7 +4,7 @@ exports.registerAssistantMode = registerAssistantMode;
4
4
  exports.getAssistantMode = getAssistantMode;
5
5
  exports.listAssistantModes = listAssistantModes;
6
6
  exports.getAssistantModeWarnings = getAssistantModeWarnings;
7
- exports.assistantModesForSubject = assistantModesForSubject;
7
+ exports.assistantModeForSubject = assistantModeForSubject;
8
8
  exports.clearAssistantModes = clearAssistantModes;
9
9
  /**
10
10
  * The mode registry — one place that knows what each conversation is.
@@ -52,48 +52,22 @@ function listAssistantModes() {
52
52
  function getAssistantModeWarnings() {
53
53
  const out = [];
54
54
  /*
55
- * 한 화면이 어느 대화를 기본으로 여는가 — 등록 순서로 정하지 않는다(ADR-0072 A17 ②). 없으면 화면이
56
- * 무엇을 열지 모르고, 둘이면 마지막에 부팅한 모듈이 조용히 이긴다. 둘 다 여기서 말한다.
55
+ * 한 화면에 대화 하나 — design/ai/screen-pattern.md §3. 같은 subject 를 두 대화가 적으면 화면은 어느 쪽을
56
+ * 열지 모르고, 고르개로 사람에게 넘기면 원칙 3 을 어긴다. 성격이 다른 물음은 그 대화가 도구를 더 연다.
57
57
  */
58
- const defaults = new Map();
59
- const claimed = new Set();
58
+ const owners = new Map();
60
59
  for (const mode of modes.values()) {
61
60
  for (const subject of mode.subjects ?? [])
62
- claimed.add(subject);
63
- for (const subject of mode.defaultFor ?? []) {
64
- defaults.set(subject, [...(defaults.get(subject) ?? []), mode.kind]);
65
- }
61
+ owners.set(subject, [...(owners.get(subject) ?? []), mode.kind]);
66
62
  }
67
- for (const subject of claimed) {
68
- const owners = defaults.get(subject) ?? [];
69
- if (owners.length === 0) {
70
- out.push({
71
- code: 'assistant-mode-subject-without-default',
72
- params: { kind: '', subject },
73
- message: `no conversation is the default for subject '${subject}' — a screen showing it has nothing to open`
74
- });
75
- }
76
- else if (owners.length > 1) {
77
- out.push({
78
- code: 'assistant-mode-subject-default-twice',
79
- params: { kind: owners[0], subject, kinds: owners },
80
- message: `subject '${subject}' has more than one default (${owners.join(', ')}) — boot order would decide it`
81
- });
82
- }
83
- }
84
- for (const mode of modes.values()) {
85
- /*
86
- * 고를 수 있는 대화에는 부를 이름이 있어야 한다 — 고르개에 종류 이름(`twin.space`)이 뜨면 그것은
87
- * 우리 내부 단위를 사람에게 떠넘기는 것이다.
88
- */
89
- const shared = (mode.subjects ?? []).some(subject => countModesFor(subject) > 1);
90
- if (shared && !mode.labelKey) {
91
- out.push({
92
- code: 'assistant-mode-offered-without-label',
93
- params: { kind: mode.kind },
94
- message: `mode '${mode.kind}' can be offered beside another on the same subject but has no labelKey`
95
- });
96
- }
63
+ for (const [subject, kinds] of owners) {
64
+ if (kinds.length < 2)
65
+ continue;
66
+ out.push({
67
+ code: 'assistant-mode-subject-claimed-twice',
68
+ params: { kind: kinds[0], subject, kinds },
69
+ message: `subject '${subject}' has more than one conversation (${kinds.join(', ')}) — a screen has one conversation; open more tools in one of them`
70
+ });
97
71
  }
98
72
  for (const mode of modes.values()) {
99
73
  /* A record the mode says it enters, which no app registered - the tool never appears and the person is told nothing. */
@@ -124,23 +98,9 @@ function getAssistantModeWarnings() {
124
98
  }
125
99
  return out;
126
100
  }
127
- /** 이 subject 종류에 서는 대화가 몇인가 — 고르개가 필요한지, 이름이 필요한지를 가른다. */
128
- function countModesFor(subject) {
129
- let count = 0;
130
- for (const mode of modes.values()) {
131
- if ((mode.subjects ?? []).includes(subject))
132
- count++;
133
- }
134
- return count;
135
- }
136
- /**
137
- * 이 subject 종류에 서는 대화들 — 등록 순서대로, 기본이 먼저.
138
- *
139
- * 화면은 이 목록으로 고르개를 낸다. 하나뿐이면 고르개를 내지 않는다(ADR-0072 A17 ①).
140
- */
141
- function assistantModesForSubject(subject) {
142
- const standing = [...modes.values()].filter(mode => (mode.subjects ?? []).includes(subject));
143
- return standing.sort((a, b) => Number((b.defaultFor ?? []).includes(subject)) - Number((a.defaultFor ?? []).includes(subject)));
101
+ /** The conversation a screen showing this kind of subject opens — one per subject (design/ai/screen-pattern.md §3). */
102
+ function assistantModeForSubject(subject) {
103
+ return [...modes.values()].find(mode => (mode.subjects ?? []).includes(subject));
144
104
  }
145
105
  /** Test only. */
146
106
  function clearAssistantModes() {
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/registry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The mode registry — one place that knows what each conversation is.\n *\n * Registration refuses what cannot be true later: a kind that is not `<app>.<surface>` (the door\n * looks a mode up by that name), an empty persona (the field this whole change exists to take off\n * the wire), and a second declaration of a kind that is already declared — two declarations of one\n * conversation disagree, and the one that wins would be whichever module booted last.\n *\n * What it does not refuse is a `/` sentence naming a tool the mode does not expose. That check\n * reads the tool registry, which fills up as modules boot, so it runs when asked rather than at\n * registration: `getAssistantModeWarnings()` is the answer as of now, and the boot report reads it\n * after every module is in.\n */\nimport { entryTargetOf } from '../data-entry/entry-target.js'\nimport { getToolSpecsFor } from '@things-factory/ai-client-base'\n\nimport type { AssistantModeSpec, AssistantModeWarning } from './types'\n\nconst modes = new Map<string, AssistantModeSpec>()\n\nconst KIND = /^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$/\n\nexport function registerAssistantMode(spec: AssistantModeSpec): void {\n if (!KIND.test(String(spec?.kind ?? ''))) {\n throw new Error(\n `[ai-assistant/assistant-mode] '${spec?.kind}' is not a mode kind. Use <app>.<surface> in lowercase — ` +\n \"'twin.space', 'plant.station', 'figure.modeller'. The client names the conversation by this.\"\n )\n }\n\n if (!String(spec.systemPrompt ?? '').trim()) {\n throw new Error(\n `[ai-assistant/assistant-mode] mode '${spec.kind}' has no systemPrompt. Who the assistant is on a ` +\n 'surface belongs to the server that owns the surface (ADR-0061 decision 7).'\n )\n }\n\n if (modes.has(spec.kind)) {\n throw new Error(\n `[ai-assistant/assistant-mode] mode '${spec.kind}' is already registered. Two declarations of one ` +\n 'conversation disagree, and which one wins would depend on boot order. Give the second one its own kind.'\n )\n }\n\n modes.set(spec.kind, spec)\n}\n\nexport function getAssistantMode(kind: string): AssistantModeSpec | undefined {\n return modes.get(kind)\n}\n\nexport function listAssistantModes(): ReadonlyArray<AssistantModeSpec> {\n return Array.from(modes.values())\n}\n\n/**\n * The promises a mode makes that its tools cannot keep, as of now.\n *\n * Read after boot: a mode may register before the category its slash sentences use.\n */\nexport function getAssistantModeWarnings(): ReadonlyArray<AssistantModeWarning> {\n const out: AssistantModeWarning[] = []\n\n /*\n * 한 화면이 어느 대화를 기본으로 여는가 — 등록 순서로 정하지 않는다(ADR-0072 A17 ②). 없으면 화면이\n * 무엇을 열지 모르고, 둘이면 마지막에 부팅한 모듈이 조용히 이긴다. 둘 다 여기서 말한다.\n */\n const defaults = new Map<string, string[]>()\n const claimed = new Set<string>()\n for (const mode of modes.values()) {\n for (const subject of mode.subjects ?? []) claimed.add(subject)\n for (const subject of mode.defaultFor ?? []) {\n defaults.set(subject, [...(defaults.get(subject) ?? []), mode.kind])\n }\n }\n for (const subject of claimed) {\n const owners = defaults.get(subject) ?? []\n if (owners.length === 0) {\n out.push({\n code: 'assistant-mode-subject-without-default',\n params: { kind: '', subject },\n message: `no conversation is the default for subject '${subject}' — a screen showing it has nothing to open`\n })\n } else if (owners.length > 1) {\n out.push({\n code: 'assistant-mode-subject-default-twice',\n params: { kind: owners[0], subject, kinds: owners },\n message: `subject '${subject}' has more than one default (${owners.join(', ')}) — boot order would decide it`\n })\n }\n }\n\n for (const mode of modes.values()) {\n /*\n * 고를 수 있는 대화에는 부를 이름이 있어야 한다 — 고르개에 종류 이름(`twin.space`)이 뜨면 그것은\n * 우리 내부 단위를 사람에게 떠넘기는 것이다.\n */\n const shared = (mode.subjects ?? []).some(subject => countModesFor(subject) > 1)\n if (shared && !mode.labelKey) {\n out.push({\n code: 'assistant-mode-offered-without-label',\n params: { kind: mode.kind },\n message: `mode '${mode.kind}' can be offered beside another on the same subject but has no labelKey`\n })\n }\n }\n\n for (const mode of modes.values()) {\n /* A record the mode says it enters, which no app registered - the tool never appears and the person is told nothing. */\n for (const key of mode.entryTargets ?? []) {\n if (entryTargetOf(key)) continue\n out.push({\n code: 'assistant-mode-entry-target-unknown',\n params: { kind: mode.kind, target: key },\n message: `mode '${mode.kind}' enters '${key}', which no application registered with registerEntryTarget`\n })\n }\n }\n\n for (const mode of modes.values()) {\n const exposed = new Set(getToolSpecsFor(mode.toolCategories ?? []).map(spec => spec.name))\n for (const slash of mode.slashTemplates ?? []) {\n for (const tool of slash.tools ?? []) {\n if (exposed.has(tool)) continue\n out.push({\n code: 'assistant-mode-slash-outside-tools',\n params: { kind: mode.kind, slash: slash.name, tool },\n message:\n `mode '${mode.kind}' offers '/${slash.name}', which needs '${tool}' — not in its tool categories ` +\n `[${(mode.toolCategories ?? []).join(', ')}]. The person presses it and the model answers without doing it.`\n })\n }\n }\n }\n return out\n}\n\n/** 이 subject 종류에 서는 대화가 몇인가 — 고르개가 필요한지, 이름이 필요한지를 가른다. */\nfunction countModesFor(subject: string): number {\n let count = 0\n for (const mode of modes.values()) {\n if ((mode.subjects ?? []).includes(subject)) count++\n }\n return count\n}\n\n/**\n * 이 subject 종류에 서는 대화들 — 등록 순서대로, 기본이 먼저.\n *\n * 화면은 이 목록으로 고르개를 낸다. 하나뿐이면 고르개를 내지 않는다(ADR-0072 A17 ①).\n */\nexport function assistantModesForSubject(subject: string): ReadonlyArray<AssistantModeSpec> {\n const standing = [...modes.values()].filter(mode => (mode.subjects ?? []).includes(subject))\n return standing.sort((a, b) => Number((b.defaultFor ?? []).includes(subject)) - Number((a.defaultFor ?? []).includes(subject)))\n}\n\n/** Test only. */\nexport function clearAssistantModes(): void {\n modes.clear()\n}\n"]}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/registry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The mode registry — one place that knows what each conversation is.\n *\n * Registration refuses what cannot be true later: a kind that is not `<app>.<surface>` (the door\n * looks a mode up by that name), an empty persona (the field this whole change exists to take off\n * the wire), and a second declaration of a kind that is already declared — two declarations of one\n * conversation disagree, and the one that wins would be whichever module booted last.\n *\n * What it does not refuse is a `/` sentence naming a tool the mode does not expose. That check\n * reads the tool registry, which fills up as modules boot, so it runs when asked rather than at\n * registration: `getAssistantModeWarnings()` is the answer as of now, and the boot report reads it\n * after every module is in.\n */\nimport { entryTargetOf } from '../data-entry/entry-target.js'\nimport { getToolSpecsFor } from '@things-factory/ai-client-base'\n\nimport type { AssistantModeSpec, AssistantModeWarning } from './types'\n\nconst modes = new Map<string, AssistantModeSpec>()\n\nconst KIND = /^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$/\n\nexport function registerAssistantMode(spec: AssistantModeSpec): void {\n if (!KIND.test(String(spec?.kind ?? ''))) {\n throw new Error(\n `[ai-assistant/assistant-mode] '${spec?.kind}' is not a mode kind. Use <app>.<surface> in lowercase — ` +\n \"'twin.space', 'plant.station', 'figure.modeller'. The client names the conversation by this.\"\n )\n }\n\n if (!String(spec.systemPrompt ?? '').trim()) {\n throw new Error(\n `[ai-assistant/assistant-mode] mode '${spec.kind}' has no systemPrompt. Who the assistant is on a ` +\n 'surface belongs to the server that owns the surface (ADR-0061 decision 7).'\n )\n }\n\n if (modes.has(spec.kind)) {\n throw new Error(\n `[ai-assistant/assistant-mode] mode '${spec.kind}' is already registered. Two declarations of one ` +\n 'conversation disagree, and which one wins would depend on boot order. Give the second one its own kind.'\n )\n }\n\n modes.set(spec.kind, spec)\n}\n\nexport function getAssistantMode(kind: string): AssistantModeSpec | undefined {\n return modes.get(kind)\n}\n\nexport function listAssistantModes(): ReadonlyArray<AssistantModeSpec> {\n return Array.from(modes.values())\n}\n\n/**\n * The promises a mode makes that its tools cannot keep, as of now.\n *\n * Read after boot: a mode may register before the category its slash sentences use.\n */\nexport function getAssistantModeWarnings(): ReadonlyArray<AssistantModeWarning> {\n const out: AssistantModeWarning[] = []\n\n /*\n * 한 화면에 대화 하나 — design/ai/screen-pattern.md §3. 같은 subject 를 두 대화가 적으면 화면은 어느 쪽을\n * 열지 모르고, 고르개로 사람에게 넘기면 원칙 3 을 어긴다. 성격이 다른 물음은 그 대화가 도구를 더 연다.\n */\n const owners = new Map<string, string[]>()\n for (const mode of modes.values()) {\n for (const subject of mode.subjects ?? []) owners.set(subject, [...(owners.get(subject) ?? []), mode.kind])\n }\n for (const [subject, kinds] of owners) {\n if (kinds.length < 2) continue\n out.push({\n code: 'assistant-mode-subject-claimed-twice',\n params: { kind: kinds[0], subject, kinds },\n message: `subject '${subject}' has more than one conversation (${kinds.join(', ')}) — a screen has one conversation; open more tools in one of them`\n })\n }\n\n for (const mode of modes.values()) {\n /* A record the mode says it enters, which no app registered - the tool never appears and the person is told nothing. */\n for (const key of mode.entryTargets ?? []) {\n if (entryTargetOf(key)) continue\n out.push({\n code: 'assistant-mode-entry-target-unknown',\n params: { kind: mode.kind, target: key },\n message: `mode '${mode.kind}' enters '${key}', which no application registered with registerEntryTarget`\n })\n }\n }\n\n for (const mode of modes.values()) {\n const exposed = new Set(getToolSpecsFor(mode.toolCategories ?? []).map(spec => spec.name))\n for (const slash of mode.slashTemplates ?? []) {\n for (const tool of slash.tools ?? []) {\n if (exposed.has(tool)) continue\n out.push({\n code: 'assistant-mode-slash-outside-tools',\n params: { kind: mode.kind, slash: slash.name, tool },\n message:\n `mode '${mode.kind}' offers '/${slash.name}', which needs '${tool}' — not in its tool categories ` +\n `[${(mode.toolCategories ?? []).join(', ')}]. The person presses it and the model answers without doing it.`\n })\n }\n }\n }\n return out\n}\n\n/** The conversation a screen showing this kind of subject opens — one per subject (design/ai/screen-pattern.md §3). */\nexport function assistantModeForSubject(subject: string): AssistantModeSpec | undefined {\n return [...modes.values()].find(mode => (mode.subjects ?? []).includes(subject))\n}\n\n/** Test only. */\nexport function clearAssistantModes(): void {\n modes.clear()\n}\n"]}
@@ -10,6 +10,7 @@ exports.assistantModeView = assistantModeView;
10
10
  * the server would answer differently.
11
11
  */
12
12
  const registry_1 = require("./registry");
13
+ const screen_rules_1 = require("./screen-rules");
13
14
  /**
14
15
  * A kind nobody registered. The sentence is the person's (ADR-0054); the code and the kind are for
15
16
  * whoever reads the log — a screen naming a conversation the server does not have is a mismatched
@@ -45,7 +46,8 @@ function resolveModeSettings(ref, t) {
45
46
  if (!mode)
46
47
  throw unknownModeError(kind, t);
47
48
  return {
48
- systemPrompt: mode.systemPrompt,
49
+ /* The app's persona, with the rules every conversation follows (design/ai/screen-pattern.md §4). */
50
+ systemPrompt: (0, screen_rules_1.conversationPrompt)(mode.systemPrompt),
49
51
  /* A mode that takes attachments can open the files sent in earlier turns (design/ai/shared-dock.md §9-4). */
50
52
  toolCategories: modeToolCategories(mode),
51
53
  requireToolOnFirstTurn: mode.requireToolOnFirstTurn,
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/resolve.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AttachmentPolicy } from '../chat-session/attachment-policy.js'\n/**\n * What the door and the screen each take from a mode.\n *\n * Both start from the same declaration, so a screen cannot show `/` sentences for a conversation\n * the server would answer differently.\n */\nimport { getAssistantMode } from './registry'\nimport type { AssistantModeSlash } from './types'\n\nexport type Translate = (key: string, params?: Record<string, any>) => string\n\nexport interface AssistantModeRef {\n /** Which conversation this is — a registered `<app>.<surface>`. */\n kind: string\n /** What it is about. Part of the conversation's lifetime; the tools read it, not the settings. */\n target?: string\n}\n\n/**\n * A kind nobody registered. The sentence is the person's (ADR-0054); the code and the kind are for\n * whoever reads the log — a screen naming a conversation the server does not have is a mismatched\n * deployment, not something the person did.\n */\nfunction unknownModeError(kind: string, t: Translate): Error {\n const error: any = new Error(\n t('ai-assistant.text.unknown-mode', {\n defaultValue: 'this screen asked for a conversation this server does not have ({kind}). reload the page.',\n kind: kind || '—'\n })\n )\n error.code = 'unknown-assistant-mode'\n error.params = { kind }\n return error\n}\n\nexport interface ModeSettings {\n systemPrompt: string\n toolCategories?: string[]\n requireToolOnFirstTurn?: boolean\n temperature?: number\n maxTokens?: number\n}\n\n/**\n * The categories a mode opens: its own, the AI 작업판 (design/ai/workspace.md), the attachment reader when it\n * takes files (design/ai/shared-dock.md §9-4), and one propose tool per record it may enter (design/ai/data-entry.md).\n */\nexport function modeToolCategories(mode: { toolCategories?: string[]; attachments?: { kinds?: string[] }; entryTargets?: string[]; readTargets?: string[] }): string[] | undefined {\n const extra = [\n /* Every conversation may put a large result on the AI 작업판 (design/ai/workspace.md). */\n 'assistant-workspace',\n ...(mode.attachments?.kinds?.length ? ['chat-attachments'] : []),\n ...(mode.entryTargets ?? []).map(key => `data-entry:${key}`),\n ...(mode.readTargets ?? []).map(key => `data-read:${key}`)\n ]\n return [...new Set([...(mode.toolCategories ?? []), ...extra])]\n}\n\n/** The conversation's settings, from the server's declaration. */\nexport function resolveModeSettings(ref: AssistantModeRef, t: Translate): ModeSettings {\n const kind = String(ref?.kind ?? '')\n const mode = kind ? getAssistantMode(kind) : undefined\n\n if (!mode) throw unknownModeError(kind, t)\n\n return {\n systemPrompt: mode.systemPrompt,\n /* A mode that takes attachments can open the files sent in earlier turns (design/ai/shared-dock.md §9-4). */\n toolCategories: modeToolCategories(mode),\n requireToolOnFirstTurn: mode.requireToolOnFirstTurn,\n temperature: mode.temperature,\n maxTokens: mode.maxTokens\n }\n}\n\nexport interface AssistantModeSlashView {\n name: string\n description: string\n template: string\n}\n\nexport interface AssistantModeView {\n kind: string\n slashTemplates: AssistantModeSlashView[]\n catalogEntries: any[]\n proposalChoices: any[]\n placeholder?: string\n /** 빈 대화의 제목과 한 줄 — 이 대화가 무엇을 하는 자리인지. */\n intro?: { title: string; subtitle: string }\n /** 빈 대화의 예시 — 이 대화에서 실제로 할 수 있는 말. */\n examples?: Array<{ label: string; items: string[] }>\n footerNotice?: string\n stagedNotice?: string\n /** What the screen may attach - the same policy the server enforces. */\n attachments?: AttachmentPolicy\n}\n\n/** What the screen shows, in the words of the request's locale. */\nexport function assistantModeView(kind: string, t: Translate): AssistantModeView {\n const mode = getAssistantMode(kind)\n if (!mode) throw unknownModeError(kind, t)\n\n return {\n kind: mode.kind,\n slashTemplates: (mode.slashTemplates ?? []).map((slash: AssistantModeSlash) => ({\n name: slash.name,\n description: t(slash.descriptionKey),\n template: t(slash.templateKey)\n })),\n catalogEntries: mode.catalogEntries ?? [],\n proposalChoices: mode.proposalChoices ?? [],\n placeholder: mode.placeholderKey ? t(mode.placeholderKey) : undefined,\n intro: mode.introKeys ? { title: t(mode.introKeys.title), subtitle: t(mode.introKeys.subtitle) } : undefined,\n examples: mode.exampleKeys?.map(group => ({\n label: t(group.labelKey),\n items: group.itemKeys.map(key => t(key))\n })),\n footerNotice: mode.footerNoticeKey ? t(mode.footerNoticeKey) : undefined,\n stagedNotice: mode.stagedNoticeKey ? t(mode.stagedNoticeKey) : undefined,\n attachments: mode.attachments\n }\n}\n"]}
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/resolve.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AttachmentPolicy } from '../chat-session/attachment-policy.js'\n/**\n * What the door and the screen each take from a mode.\n *\n * Both start from the same declaration, so a screen cannot show `/` sentences for a conversation\n * the server would answer differently.\n */\nimport { getAssistantMode } from './registry'\nimport { conversationPrompt } from './screen-rules'\nimport type { AssistantModeSlash } from './types'\n\nexport type Translate = (key: string, params?: Record<string, any>) => string\n\nexport interface AssistantModeRef {\n /** Which conversation this is — a registered `<app>.<surface>`. */\n kind: string\n /** What it is about. Part of the conversation's lifetime; the tools read it, not the settings. */\n target?: string\n}\n\n/**\n * A kind nobody registered. The sentence is the person's (ADR-0054); the code and the kind are for\n * whoever reads the log — a screen naming a conversation the server does not have is a mismatched\n * deployment, not something the person did.\n */\nfunction unknownModeError(kind: string, t: Translate): Error {\n const error: any = new Error(\n t('ai-assistant.text.unknown-mode', {\n defaultValue: 'this screen asked for a conversation this server does not have ({kind}). reload the page.',\n kind: kind || '—'\n })\n )\n error.code = 'unknown-assistant-mode'\n error.params = { kind }\n return error\n}\n\nexport interface ModeSettings {\n systemPrompt: string\n toolCategories?: string[]\n requireToolOnFirstTurn?: boolean\n temperature?: number\n maxTokens?: number\n}\n\n/**\n * The categories a mode opens: its own, the AI 작업판 (design/ai/workspace.md), the attachment reader when it\n * takes files (design/ai/shared-dock.md §9-4), and one propose tool per record it may enter (design/ai/data-entry.md).\n */\nexport function modeToolCategories(mode: { toolCategories?: string[]; attachments?: { kinds?: string[] }; entryTargets?: string[]; readTargets?: string[] }): string[] | undefined {\n const extra = [\n /* Every conversation may put a large result on the AI 작업판 (design/ai/workspace.md). */\n 'assistant-workspace',\n ...(mode.attachments?.kinds?.length ? ['chat-attachments'] : []),\n ...(mode.entryTargets ?? []).map(key => `data-entry:${key}`),\n ...(mode.readTargets ?? []).map(key => `data-read:${key}`)\n ]\n return [...new Set([...(mode.toolCategories ?? []), ...extra])]\n}\n\n/** The conversation's settings, from the server's declaration. */\nexport function resolveModeSettings(ref: AssistantModeRef, t: Translate): ModeSettings {\n const kind = String(ref?.kind ?? '')\n const mode = kind ? getAssistantMode(kind) : undefined\n\n if (!mode) throw unknownModeError(kind, t)\n\n return {\n /* The app's persona, with the rules every conversation follows (design/ai/screen-pattern.md §4). */\n systemPrompt: conversationPrompt(mode.systemPrompt),\n /* A mode that takes attachments can open the files sent in earlier turns (design/ai/shared-dock.md §9-4). */\n toolCategories: modeToolCategories(mode),\n requireToolOnFirstTurn: mode.requireToolOnFirstTurn,\n temperature: mode.temperature,\n maxTokens: mode.maxTokens\n }\n}\n\nexport interface AssistantModeSlashView {\n name: string\n description: string\n template: string\n}\n\nexport interface AssistantModeView {\n kind: string\n slashTemplates: AssistantModeSlashView[]\n catalogEntries: any[]\n proposalChoices: any[]\n placeholder?: string\n /** 빈 대화의 제목과 한 줄 — 이 대화가 무엇을 하는 자리인지. */\n intro?: { title: string; subtitle: string }\n /** 빈 대화의 예시 — 이 대화에서 실제로 할 수 있는 말. */\n examples?: Array<{ label: string; items: string[] }>\n footerNotice?: string\n stagedNotice?: string\n /** What the screen may attach - the same policy the server enforces. */\n attachments?: AttachmentPolicy\n}\n\n/** What the screen shows, in the words of the request's locale. */\nexport function assistantModeView(kind: string, t: Translate): AssistantModeView {\n const mode = getAssistantMode(kind)\n if (!mode) throw unknownModeError(kind, t)\n\n return {\n kind: mode.kind,\n slashTemplates: (mode.slashTemplates ?? []).map((slash: AssistantModeSlash) => ({\n name: slash.name,\n description: t(slash.descriptionKey),\n template: t(slash.templateKey)\n })),\n catalogEntries: mode.catalogEntries ?? [],\n proposalChoices: mode.proposalChoices ?? [],\n placeholder: mode.placeholderKey ? t(mode.placeholderKey) : undefined,\n intro: mode.introKeys ? { title: t(mode.introKeys.title), subtitle: t(mode.introKeys.subtitle) } : undefined,\n examples: mode.exampleKeys?.map(group => ({\n label: t(group.labelKey),\n items: group.itemKeys.map(key => t(key))\n })),\n footerNotice: mode.footerNoticeKey ? t(mode.footerNoticeKey) : undefined,\n stagedNotice: mode.stagedNoticeKey ? t(mode.stagedNoticeKey) : undefined,\n attachments: mode.attachments\n }\n}\n"]}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The rules every conversation follows, whatever app declared it (operato-application design/ai/screen-pattern.md).
3
+ *
4
+ * Five apps carried their own copy of the absence rule, word for word or nearly. A rule every conversation needs
5
+ * belongs to the door that builds every conversation's prompt, so an app's prompt says only who the assistant is
6
+ * there and what it may not claim.
7
+ *
8
+ * Model-facing text is English (the AI standard's A11); the person's language comes from the request.
9
+ */
10
+ /** §4-2 — an absence or a refusal is a fact, with what the person can do. */
11
+ export declare const ABSENCE_RULE: string;
12
+ /** §4-1 — the reply names the next request, and the input offers it. */
13
+ export declare const NEXT_REQUEST_RULE: string;
14
+ /** A mode's persona with the rules every conversation follows. */
15
+ export declare function conversationPrompt(modePrompt: string): string;
16
+ /**
17
+ * The reply without its `Next:` line, and the request that line named.
18
+ *
19
+ * Only the last non-empty line counts: a `Next:` in the middle of an answer is part of the answer. The line is
20
+ * taken off before the reply is stored, so a conversation read later shows the answer alone.
21
+ */
22
+ export declare function splitNextRequest(reply: string): {
23
+ reply: string;
24
+ suggestion?: string;
25
+ };
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ /**
3
+ * The rules every conversation follows, whatever app declared it (operato-application design/ai/screen-pattern.md).
4
+ *
5
+ * Five apps carried their own copy of the absence rule, word for word or nearly. A rule every conversation needs
6
+ * belongs to the door that builds every conversation's prompt, so an app's prompt says only who the assistant is
7
+ * there and what it may not claim.
8
+ *
9
+ * Model-facing text is English (the AI standard's A11); the person's language comes from the request.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.NEXT_REQUEST_RULE = exports.ABSENCE_RULE = void 0;
13
+ exports.conversationPrompt = conversationPrompt;
14
+ exports.splitNextRequest = splitNextRequest;
15
+ /** §4-2 — an absence or a refusal is a fact, with what the person can do. */
16
+ exports.ABSENCE_RULE = 'When a tool says something is absent or refuses, state that and what the person can do. ' +
17
+ 'Do not guess why, and do not describe our machinery - words like passed, delivered or loaded name our plumbing, not their situation.';
18
+ /** The line a reply ends with when a next request is natural (§4-1). */
19
+ const NEXT_PREFIX = 'Next:';
20
+ /** §4-1 — the reply names the next request, and the input offers it. */
21
+ exports.NEXT_REQUEST_RULE = `After answering, when there is a natural next request, end the reply with one last line "${NEXT_PREFIX} <request>": ` +
22
+ "the request the person is most likely to make next, written as they would type it, in the language they wrote in. " +
23
+ 'Offer only what this conversation can do with its tools. When nothing natural follows, leave the line out.';
24
+ /** A mode's persona with the rules every conversation follows. */
25
+ function conversationPrompt(modePrompt) {
26
+ return [modePrompt.trim(), exports.ABSENCE_RULE, exports.NEXT_REQUEST_RULE].join('\n\n');
27
+ }
28
+ /**
29
+ * The reply without its `Next:` line, and the request that line named.
30
+ *
31
+ * Only the last non-empty line counts: a `Next:` in the middle of an answer is part of the answer. The line is
32
+ * taken off before the reply is stored, so a conversation read later shows the answer alone.
33
+ */
34
+ function splitNextRequest(reply) {
35
+ const text = String(reply ?? '');
36
+ const lines = text.replace(/\s+$/, '').split('\n');
37
+ const last = lines[lines.length - 1] ?? '';
38
+ const match = /^\s*(?:\*\*)?Next:(?:\*\*)?\s*(.+?)\s*$/.exec(last);
39
+ if (!match)
40
+ return { reply: text };
41
+ const suggestion = match[1].replace(/^["“'`]+|["”'`]+$/g, '').trim();
42
+ const rest = lines.slice(0, -1).join('\n').replace(/\s+$/, '');
43
+ if (!suggestion || !rest)
44
+ return { reply: text };
45
+ return { reply: rest, suggestion };
46
+ }
47
+ //# sourceMappingURL=screen-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screen-rules.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/screen-rules.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAiBH,gDAEC;AAQD,4CAWC;AApCD,6EAA6E;AAChE,QAAA,YAAY,GACvB,0FAA0F;IAC1F,sIAAsI,CAAA;AAExI,wEAAwE;AACxE,MAAM,WAAW,GAAG,OAAO,CAAA;AAE3B,wEAAwE;AAC3D,QAAA,iBAAiB,GAC5B,4FAA4F,WAAW,eAAe;IACtH,oHAAoH;IACpH,4GAA4G,CAAA;AAE9G,kEAAkE;AAClE,SAAgB,kBAAkB,CAAC,UAAkB;IACnD,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,oBAAY,EAAE,yBAAiB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;AAC1E,CAAC;AAED;;;;;GAKG;AACH,SAAgB,gBAAgB,CAAC,KAAa;IAC5C,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;IAChC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAClD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;IAC1C,MAAM,KAAK,GAAG,yCAAyC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAClE,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IAElC,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACpE,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAC9D,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IAChD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,CAAA;AACpC,CAAC","sourcesContent":["/**\n * The rules every conversation follows, whatever app declared it (operato-application design/ai/screen-pattern.md).\n *\n * Five apps carried their own copy of the absence rule, word for word or nearly. A rule every conversation needs\n * belongs to the door that builds every conversation's prompt, so an app's prompt says only who the assistant is\n * there and what it may not claim.\n *\n * Model-facing text is English (the AI standard's A11); the person's language comes from the request.\n */\n\n/** §4-2 — an absence or a refusal is a fact, with what the person can do. */\nexport const ABSENCE_RULE =\n 'When a tool says something is absent or refuses, state that and what the person can do. ' +\n 'Do not guess why, and do not describe our machinery - words like passed, delivered or loaded name our plumbing, not their situation.'\n\n/** The line a reply ends with when a next request is natural (§4-1). */\nconst NEXT_PREFIX = 'Next:'\n\n/** §4-1 — the reply names the next request, and the input offers it. */\nexport const NEXT_REQUEST_RULE =\n `After answering, when there is a natural next request, end the reply with one last line \"${NEXT_PREFIX} <request>\": ` +\n \"the request the person is most likely to make next, written as they would type it, in the language they wrote in. \" +\n 'Offer only what this conversation can do with its tools. When nothing natural follows, leave the line out.'\n\n/** A mode's persona with the rules every conversation follows. */\nexport function conversationPrompt(modePrompt: string): string {\n return [modePrompt.trim(), ABSENCE_RULE, NEXT_REQUEST_RULE].join('\\n\\n')\n}\n\n/**\n * The reply without its `Next:` line, and the request that line named.\n *\n * Only the last non-empty line counts: a `Next:` in the middle of an answer is part of the answer. The line is\n * taken off before the reply is stored, so a conversation read later shows the answer alone.\n */\nexport function splitNextRequest(reply: string): { reply: string; suggestion?: string } {\n const text = String(reply ?? '')\n const lines = text.replace(/\\s+$/, '').split('\\n')\n const last = lines[lines.length - 1] ?? ''\n const match = /^\\s*(?:\\*\\*)?Next:(?:\\*\\*)?\\s*(.+?)\\s*$/.exec(last)\n if (!match) return { reply: text }\n\n const suggestion = match[1].replace(/^[\"“'`]+|[\"”'`]+$/g, '').trim()\n const rest = lines.slice(0, -1).join('\\n').replace(/\\s+$/, '')\n if (!suggestion || !rest) return { reply: text }\n return { reply: rest, suggestion }\n}\n"]}
@@ -26,30 +26,14 @@ export interface AssistantModeSpec {
26
26
  /** `<app>.<surface>` — `twin.space`, `plant.station`, `figure.modeller`. */
27
27
  kind: string;
28
28
  /**
29
- * Which kinds of subject this conversation stands for — `['board', 'space']` (ADR-0072 A17 ①).
29
+ * Which kinds of subject this conversation is for — `['board']`, `['space']`.
30
30
  *
31
- * A screen says what it is showing (`context.subject`), and these say which conversations that
32
- * subject can carry. It is a list of types and not a predicate on purpose: a list can be counted
33
- * at boot and pinned by a test, while a function can only be run.
34
- *
35
- * On the twin modeller nothing could be asked about operations, because an open board pinned the
36
- * authoring conversation and that conversation opens one category. A conversation that also
37
- * stands for `board` is how the same screen carries both.
31
+ * A screen says what it is showing (`context.subject`) and opens the one conversation for it: a screen has one
32
+ * conversation (design/ai/screen-pattern.md §3). A list of types, so it can be counted at boot and pinned by a
33
+ * test; two modes naming one subject is a boot warning.
38
34
  */
39
35
  subjects?: string[];
40
- /**
41
- * The conversation a screen opens when the person has not chosen — one per subject type
42
- * (ADR-0072 A17 ②).
43
- *
44
- * Registration order must not decide this: whichever module booted last would silently become
45
- * the default. Exactly one mode declares each subject type, and the boot report says so when a
46
- * type has none or more than one.
47
- */
48
- defaultFor?: string[];
49
- /**
50
- * i18n key for what this conversation is called where a person chooses between them —
51
- * "이 그림 고치기", "이 현장 지금 어때". A mode a screen may offer needs one.
52
- */
36
+ /** i18n key for what this conversation is called in the conversation list. */
53
37
  labelKey?: string;
54
38
  /** Who the assistant is here, and what it must not claim to do. The server's, not the caller's. */
55
39
  systemPrompt: string;
@@ -115,11 +99,10 @@ export interface AssistantModeSpec {
115
99
  export interface AssistantModeWarning {
116
100
  /**
117
101
  * `assistant-mode-slash-outside-tools` — a `/` sentence needs a tool this mode does not open.
118
- * `assistant-mode-subject-without-default` — a subject type no conversation claims as its default.
119
- * `assistant-mode-subject-default-twice` — two conversations claim the same subject type.
120
- * `assistant-mode-offered-without-label` — a conversation a screen may offer with nothing to call it.
102
+ * `assistant-mode-subject-claimed-twice` — two conversations name the same subject type.
103
+ * `assistant-mode-entry-target-unknown` — a record the mode enters that no application registered.
121
104
  */
122
- code: 'assistant-mode-slash-outside-tools' | 'assistant-mode-subject-without-default' | 'assistant-mode-subject-default-twice' | 'assistant-mode-offered-without-label' | 'assistant-mode-entry-target-unknown';
105
+ code: 'assistant-mode-slash-outside-tools' | 'assistant-mode-subject-claimed-twice' | 'assistant-mode-entry-target-unknown';
123
106
  params: {
124
107
  kind: string;
125
108
  slash?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { AttachmentPolicy } from '../chat-session/attachment-policy.js'\n/**\n * What a conversation is, declared by the app server that owns the screen (ADR-0061 decision 7).\n *\n * ADR-0058 put these five in one value so they could not fall out of step, but the value lived in\n * the browser and travelled on every request: the persona, the tool categories, the model and the\n * limits were the caller's to choose. A caller choosing its own tool categories is a caller\n * choosing its own authority, so the declaration moved here and the client sends only which one it\n * is talking in (`kind`) and what about (`target`).\n */\n\n/** A `/` sentence this mode offers, and what it needs to be answerable. */\nexport interface AssistantModeSlash {\n /** Short name shown in the list. */\n name: string\n /** i18n key for the one-line description. The server translates it into the request's locale. */\n descriptionKey: string\n /** i18n key for the sentence that lands in the input. */\n templateKey: string\n /**\n * The tools this sentence needs. A mode must not offer work it cannot do (ADR-0058 decision 3),\n * and a name outside `toolCategories` is reported at boot rather than when a person presses it.\n */\n tools?: string[]\n}\n\nexport interface AssistantModeSpec {\n /** `<app>.<surface>` — `twin.space`, `plant.station`, `figure.modeller`. */\n kind: string\n\n /**\n * Which kinds of subject this conversation stands for — `['board', 'space']` (ADR-0072 A17 ①).\n *\n * A screen says what it is showing (`context.subject`), and these say which conversations that\n * subject can carry. It is a list of types and not a predicate on purpose: a list can be counted\n * at boot and pinned by a test, while a function can only be run.\n *\n * On the twin modeller nothing could be asked about operations, because an open board pinned the\n * authoring conversation and that conversation opens one category. A conversation that also\n * stands for `board` is how the same screen carries both.\n */\n subjects?: string[]\n\n /**\n * The conversation a screen opens when the person has not chosen — one per subject type\n * (ADR-0072 A17 ②).\n *\n * Registration order must not decide this: whichever module booted last would silently become\n * the default. Exactly one mode declares each subject type, and the boot report says so when a\n * type has none or more than one.\n */\n defaultFor?: string[]\n\n /**\n * i18n key for what this conversation is called where a person chooses between them —\n * \"이 그림 고치기\", \"이 현장 지금 어때\". A mode a screen may offer needs one.\n */\n labelKey?: string\n /** Who the assistant is here, and what it must not claim to do. The server's, not the caller's. */\n systemPrompt: string\n /** Which tool categories this conversation may use. Absent or empty exposes none. */\n toolCategories?: string[]\n /** Make the model read before it answers — for surfaces whose subject is live state. */\n requireToolOnFirstTurn?: boolean\n /** Limits are policy, not a request field. The model is the domain's AI agent's (design/ai/agent-settings.md). */\n temperature?: number\n maxTokens?: number\n /** What `/` offers. Declared as keys; the server translates. */\n slashTemplates?: AssistantModeSlash[]\n /** What `@` offers. */\n catalogEntries?: any[]\n /**\n * i18n key for the words in the empty input.\n *\n * The screen says what this conversation is for, and that differs by mode: figure's modeller had\n * \"describe the figure you want\" while the person was there to change the figure already open.\n */\n placeholderKey?: string\n\n /**\n * 빈 대화가 무엇을 말하는가 — 제목과 한 줄 (ADR-0061 결정 7).\n *\n * 자리표와 `/` 는 대화를 따라가는데 이 블록만 host 속성에 남아 있었다. 그래서 머리에는 「이 도형\n * 고치기」가 서 있고 본문은 「만들 Figure 를 설명하세요」라고 적혀 있었다(2026-09-18, Figure 레인\n * :3300). 한 대화가 화면에 무엇을 말하는가는 한 자리에서 나와야 한다.\n */\n introKeys?: { title: string; subtitle: string }\n\n /**\n * 빈 대화의 예시 — 「이 대화에서 할 수 있는 말」.\n *\n * 예시는 그 도구로 **실제로 할 수 있는 말**이어야 한다. 할 수 없는 것을 예시로 두면 빈 화면이 거짓을\n * 가르친다. 대화마다 열리는 도구가 다르므로 예시도 대화를 따라간다.\n */\n exampleKeys?: Array<{ labelKey: string; itemKeys: string[] }>\n\n /**\n * What this conversation accepts as attachments (design/ai/shared-dock.md §9-1). Absent: nothing - the\n * screen offers no attach button, so no file is taken that the model would never see.\n */\n attachments?: AttachmentPolicy\n /**\n * Which records this conversation may enter from material (design/ai/data-entry.md) - keys registered\n * with `registerEntryTarget`. Each opens that target's propose tool; its door still decides who gets it.\n */\n entryTargets?: string[]\n /** The lists of the application the conversation may read - read targets by key (design/ai/read-targets.md). */\n readTargets?: string[]\n /** i18n key for the line under an empty conversation. */\n footerNoticeKey?: string\n /** i18n key for the line shown once the host reports a proposal staged. */\n stagedNoticeKey?: string\n /** Follow-up chips for a proposal that arrived without its own. */\n proposalChoices?: any[]\n}\n\n/** A declaration that registered but is not what the contract asks for. Code + params, not prose. */\nexport interface AssistantModeWarning {\n /**\n * `assistant-mode-slash-outside-tools` — a `/` sentence needs a tool this mode does not open.\n * `assistant-mode-subject-without-default` — a subject type no conversation claims as its default.\n * `assistant-mode-subject-default-twice` — two conversations claim the same subject type.\n * `assistant-mode-offered-without-label` — a conversation a screen may offer with nothing to call it.\n */\n code:\n | 'assistant-mode-slash-outside-tools'\n | 'assistant-mode-subject-without-default'\n | 'assistant-mode-subject-default-twice'\n | 'assistant-mode-offered-without-label'\n | 'assistant-mode-entry-target-unknown'\n params: { kind: string; slash?: string; tool?: string; subject?: string; kinds?: string[]; target?: string }\n /** For a person reading the boot log; callers act on the code and params. */\n message: string\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { AttachmentPolicy } from '../chat-session/attachment-policy.js'\n/**\n * What a conversation is, declared by the app server that owns the screen (ADR-0061 decision 7).\n *\n * ADR-0058 put these five in one value so they could not fall out of step, but the value lived in\n * the browser and travelled on every request: the persona, the tool categories, the model and the\n * limits were the caller's to choose. A caller choosing its own tool categories is a caller\n * choosing its own authority, so the declaration moved here and the client sends only which one it\n * is talking in (`kind`) and what about (`target`).\n */\n\n/** A `/` sentence this mode offers, and what it needs to be answerable. */\nexport interface AssistantModeSlash {\n /** Short name shown in the list. */\n name: string\n /** i18n key for the one-line description. The server translates it into the request's locale. */\n descriptionKey: string\n /** i18n key for the sentence that lands in the input. */\n templateKey: string\n /**\n * The tools this sentence needs. A mode must not offer work it cannot do (ADR-0058 decision 3),\n * and a name outside `toolCategories` is reported at boot rather than when a person presses it.\n */\n tools?: string[]\n}\n\nexport interface AssistantModeSpec {\n /** `<app>.<surface>` — `twin.space`, `plant.station`, `figure.modeller`. */\n kind: string\n\n /**\n * Which kinds of subject this conversation is for — `['board']`, `['space']`.\n *\n * A screen says what it is showing (`context.subject`) and opens the one conversation for it: a screen has one\n * conversation (design/ai/screen-pattern.md §3). A list of types, so it can be counted at boot and pinned by a\n * test; two modes naming one subject is a boot warning.\n */\n subjects?: string[]\n\n /** i18n key for what this conversation is called in the conversation list. */\n labelKey?: string\n /** Who the assistant is here, and what it must not claim to do. The server's, not the caller's. */\n systemPrompt: string\n /** Which tool categories this conversation may use. Absent or empty exposes none. */\n toolCategories?: string[]\n /** Make the model read before it answers — for surfaces whose subject is live state. */\n requireToolOnFirstTurn?: boolean\n /** Limits are policy, not a request field. The model is the domain's AI agent's (design/ai/agent-settings.md). */\n temperature?: number\n maxTokens?: number\n /** What `/` offers. Declared as keys; the server translates. */\n slashTemplates?: AssistantModeSlash[]\n /** What `@` offers. */\n catalogEntries?: any[]\n /**\n * i18n key for the words in the empty input.\n *\n * The screen says what this conversation is for, and that differs by mode: figure's modeller had\n * \"describe the figure you want\" while the person was there to change the figure already open.\n */\n placeholderKey?: string\n\n /**\n * 빈 대화가 무엇을 말하는가 — 제목과 한 줄 (ADR-0061 결정 7).\n *\n * 자리표와 `/` 는 대화를 따라가는데 이 블록만 host 속성에 남아 있었다. 그래서 머리에는 「이 도형\n * 고치기」가 서 있고 본문은 「만들 Figure 를 설명하세요」라고 적혀 있었다(2026-09-18, Figure 레인\n * :3300). 한 대화가 화면에 무엇을 말하는가는 한 자리에서 나와야 한다.\n */\n introKeys?: { title: string; subtitle: string }\n\n /**\n * 빈 대화의 예시 — 「이 대화에서 할 수 있는 말」.\n *\n * 예시는 그 도구로 **실제로 할 수 있는 말**이어야 한다. 할 수 없는 것을 예시로 두면 빈 화면이 거짓을\n * 가르친다. 대화마다 열리는 도구가 다르므로 예시도 대화를 따라간다.\n */\n exampleKeys?: Array<{ labelKey: string; itemKeys: string[] }>\n\n /**\n * What this conversation accepts as attachments (design/ai/shared-dock.md §9-1). Absent: nothing - the\n * screen offers no attach button, so no file is taken that the model would never see.\n */\n attachments?: AttachmentPolicy\n /**\n * Which records this conversation may enter from material (design/ai/data-entry.md) - keys registered\n * with `registerEntryTarget`. Each opens that target's propose tool; its door still decides who gets it.\n */\n entryTargets?: string[]\n /** The lists of the application the conversation may read - read targets by key (design/ai/read-targets.md). */\n readTargets?: string[]\n /** i18n key for the line under an empty conversation. */\n footerNoticeKey?: string\n /** i18n key for the line shown once the host reports a proposal staged. */\n stagedNoticeKey?: string\n /** Follow-up chips for a proposal that arrived without its own. */\n proposalChoices?: any[]\n}\n\n/** A declaration that registered but is not what the contract asks for. Code + params, not prose. */\nexport interface AssistantModeWarning {\n /**\n * `assistant-mode-slash-outside-tools` — a `/` sentence needs a tool this mode does not open.\n * `assistant-mode-subject-claimed-twice` — two conversations name the same subject type.\n * `assistant-mode-entry-target-unknown` — a record the mode enters that no application registered.\n */\n code: 'assistant-mode-slash-outside-tools' | 'assistant-mode-subject-claimed-twice' | 'assistant-mode-entry-target-unknown'\n params: { kind: string; slash?: string; tool?: string; subject?: string; kinds?: string[]; target?: string }\n /** For a person reading the boot log; callers act on the code and params. */\n message: string\n}\n"]}
@@ -48,3 +48,14 @@ export declare function modeWording(kind: string, dictionary: Record<string, str
48
48
  export declare function missingWording(kind: string, dictionary: Record<string, string>): string[];
49
49
  /** 이 모드가 자기 도구로 못 하는 일을 제안하는가 — 부팅 보고와 같은 답. */
50
50
  export declare function modePromisesItCannotKeep(kind: string): string[];
51
+ /**
52
+ * What an app's prompt carries that every conversation already gets (design/ai/screen-pattern.md §6 · §7).
53
+ *
54
+ * The absence rule is added by the door to every conversation. A copy in an app's prompt says it twice, and the
55
+ * copies drift - five apps had five spellings of it.
56
+ */
57
+ export declare function modeCarriesSharedRules(kind: string): string[];
58
+ /** Subjects that more than one conversation names - a screen has one (design/ai/screen-pattern.md §3). */
59
+ export declare function subjectsClaimedTwice(): string[];
60
+ /** The line the model reads for an open subject - an app's test checks its describer names its own record. */
61
+ export declare function openSubjectLineFor(anchorType: string, anchorId: string, domainId: string, state?: any): Promise<string | undefined>;
@@ -5,6 +5,9 @@ exports.runRegisteredMode = runRegisteredMode;
5
5
  exports.modeWording = modeWording;
6
6
  exports.missingWording = missingWording;
7
7
  exports.modePromisesItCannotKeep = modePromisesItCannotKeep;
8
+ exports.modeCarriesSharedRules = modeCarriesSharedRules;
9
+ exports.subjectsClaimedTwice = subjectsClaimedTwice;
10
+ exports.openSubjectLineFor = openSubjectLineFor;
8
11
  /*
9
12
  * 등록된 모드가 **선언한 대로 도는가** — 글자가 아니라 문을 지나서 본다.
10
13
  *
@@ -107,4 +110,29 @@ function modePromisesItCannotKeep(kind) {
107
110
  .filter(warning => warning.params.kind === kind)
108
111
  .map(warning => `${warning.code}:${warning.params.slash}→${warning.params.tool}`);
109
112
  }
113
+ /**
114
+ * What an app's prompt carries that every conversation already gets (design/ai/screen-pattern.md §6 · §7).
115
+ *
116
+ * The absence rule is added by the door to every conversation. A copy in an app's prompt says it twice, and the
117
+ * copies drift - five apps had five spellings of it.
118
+ */
119
+ function modeCarriesSharedRules(kind) {
120
+ const prompt = String((0, registry_js_1.getAssistantMode)(kind)?.systemPrompt ?? '');
121
+ const found = [];
122
+ if (/name our plumbing|do not describe our machinery/i.test(prompt))
123
+ found.push('absence-rule-copy');
124
+ if (/^\s*Next:/im.test(prompt) || /end the reply with one last line/i.test(prompt))
125
+ found.push('next-request-rule-copy');
126
+ return found;
127
+ }
128
+ /** Subjects that more than one conversation names - a screen has one (design/ai/screen-pattern.md §3). */
129
+ function subjectsClaimedTwice() {
130
+ return (0, registry_js_1.getAssistantModeWarnings)()
131
+ .filter(warning => warning.code === 'assistant-mode-subject-claimed-twice')
132
+ .map(warning => String(warning.params.subject));
133
+ }
134
+ /** The line the model reads for an open subject - an app's test checks its describer names its own record. */
135
+ function openSubjectLineFor(anchorType, anchorId, domainId, state) {
136
+ return (0, ai_client_base_1.openSubjectLine)({ anchorType, anchorId, domain: { id: domainId }, state });
137
+ }
110
138
  //# sourceMappingURL=mode-conformance.js.map