@things-factory/ai-assistant 10.1.90 → 10.1.91

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 (53) hide show
  1. package/client/components/assistant-chat.ts +26 -0
  2. package/client/components/assistant-dock.ts +9 -1
  3. package/client/components/workspace-view.ts +428 -0
  4. package/client/pages/assistant-workspace-page.ts +237 -0
  5. package/client/route.ts +9 -0
  6. package/client/utils/workspace.ts +89 -0
  7. package/dist-client/components/assistant-chat.d.ts +2 -0
  8. package/dist-client/components/assistant-chat.js +27 -0
  9. package/dist-client/components/assistant-chat.js.map +1 -1
  10. package/dist-client/components/assistant-dock.d.ts +1 -0
  11. package/dist-client/components/assistant-dock.js +9 -1
  12. package/dist-client/components/assistant-dock.js.map +1 -1
  13. package/dist-client/components/workspace-view.d.ts +19 -0
  14. package/dist-client/components/workspace-view.js +416 -0
  15. package/dist-client/components/workspace-view.js.map +1 -0
  16. package/dist-client/pages/assistant-workspace-page.d.ts +27 -0
  17. package/dist-client/pages/assistant-workspace-page.js +263 -0
  18. package/dist-client/pages/assistant-workspace-page.js.map +1 -0
  19. package/dist-client/route.d.ts +1 -0
  20. package/dist-client/route.js +10 -0
  21. package/dist-client/route.js.map +1 -0
  22. package/dist-client/tsconfig.tsbuildinfo +1 -1
  23. package/dist-client/utils/workspace.d.ts +40 -0
  24. package/dist-client/utils/workspace.js +68 -0
  25. package/dist-client/utils/workspace.js.map +1 -0
  26. package/dist-server/service/assistant-mode/resolve.d.ts +2 -2
  27. package/dist-server/service/assistant-mode/resolve.js +5 -3
  28. package/dist-server/service/assistant-mode/resolve.js.map +1 -1
  29. package/dist-server/service/index.d.ts +2 -0
  30. package/dist-server/service/index.js +3 -0
  31. package/dist-server/service/index.js.map +1 -1
  32. package/dist-server/service/workspace/workspace-blocks.d.ts +24 -0
  33. package/dist-server/service/workspace/workspace-blocks.js +118 -0
  34. package/dist-server/service/workspace/workspace-blocks.js.map +1 -0
  35. package/dist-server/service/workspace/workspace-tools.d.ts +24 -0
  36. package/dist-server/service/workspace/workspace-tools.js +65 -0
  37. package/dist-server/service/workspace/workspace-tools.js.map +1 -0
  38. package/dist-server/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +2 -2
  40. package/server/service/assistant-mode/resolve.test.ts +2 -1
  41. package/server/service/assistant-mode/resolve.ts +5 -3
  42. package/server/service/data-entry/data-entry.test.ts +2 -2
  43. package/server/service/index.ts +3 -0
  44. package/server/service/workspace/workspace-blocks.ts +127 -0
  45. package/server/service/workspace/workspace-tools.ts +66 -0
  46. package/server/service/workspace/workspace.test.ts +58 -0
  47. package/test/workspace.test.ts +54 -0
  48. package/things-factory.config.js +6 -1
  49. package/translations/en.json +21 -1
  50. package/translations/ja.json +21 -1
  51. package/translations/ko.json +21 -1
  52. package/translations/ms.json +21 -1
  53. package/translations/zh.json +21 -1
@@ -0,0 +1,40 @@
1
+ export interface WorkspaceBlockView {
2
+ kind: string;
3
+ [key: string]: any;
4
+ }
5
+ export interface WorkspaceView {
6
+ name: string;
7
+ title: string;
8
+ summary?: string;
9
+ blocks: WorkspaceBlockView[];
10
+ }
11
+ export interface WorkspaceVersion {
12
+ /** 1-based, in the order the conversation made them. */
13
+ version: number;
14
+ messageId: string;
15
+ createdAt?: string;
16
+ workspace: WorkspaceView;
17
+ }
18
+ export declare function isWorkspaceProposal(proposal: any): boolean;
19
+ /**
20
+ * Every version of the named workspace in a conversation, oldest first. A message the reader may not see
21
+ * comes back redacted without its proposals, so a version behind a door is simply not among them.
22
+ */
23
+ export declare function workspaceVersions(messages: {
24
+ id: string;
25
+ createdAt?: string;
26
+ proposalsJson?: any[] | null;
27
+ }[], name: string): WorkspaceVersion[];
28
+ /** The version of a card within its conversation - "2 of 3" on the card and on the page. */
29
+ export declare function versionOfCard(proposalsInOrder: any[][], name: string, key: string): {
30
+ version: number;
31
+ of: number;
32
+ };
33
+ export declare function workspaceAddress(sessionId: string, name: string, version?: number): string;
34
+ /** Sorts by one column - numbers as numbers, empty last, the rest by the reader's collation. */
35
+ export declare function sortRows(rows: Record<string, any>[], column: string, direction: 'asc' | 'desc', locale?: string): Record<string, any>[];
36
+ /** The table as CSV - the heading row in the column labels, a BOM so a spreadsheet reads Korean. */
37
+ export declare function tableCsv(columns: {
38
+ name: string;
39
+ label?: string;
40
+ }[], rows: Record<string, any>[]): string;
@@ -0,0 +1,68 @@
1
+ /*
2
+ * The AI 작업판 on the screen - which versions a workspace has, and the table's sort and CSV
3
+ * (operato-application/design/ai/workspace.md). Pure - tested without a browser.
4
+ */
5
+ export function isWorkspaceProposal(proposal) {
6
+ return proposal?.kind === 'workspace' && !!proposal?.workspace?.name;
7
+ }
8
+ /**
9
+ * Every version of the named workspace in a conversation, oldest first. A message the reader may not see
10
+ * comes back redacted without its proposals, so a version behind a door is simply not among them.
11
+ */
12
+ export function workspaceVersions(messages, name) {
13
+ const versions = [];
14
+ for (const message of messages) {
15
+ for (const proposal of message.proposalsJson ?? []) {
16
+ if (isWorkspaceProposal(proposal) && proposal.workspace.name === name) {
17
+ versions.push({ version: versions.length + 1, messageId: message.id, createdAt: message.createdAt, workspace: proposal.workspace });
18
+ }
19
+ }
20
+ }
21
+ return versions;
22
+ }
23
+ /** The version of a card within its conversation - "2 of 3" on the card and on the page. */
24
+ export function versionOfCard(proposalsInOrder, name, key) {
25
+ let version = 0;
26
+ let of = 0;
27
+ for (const list of proposalsInOrder)
28
+ for (const one of list ?? []) {
29
+ if (!isWorkspaceProposal(one) || one.workspace.name !== name)
30
+ continue;
31
+ of += 1;
32
+ if (one.key === key)
33
+ version = of;
34
+ }
35
+ return { version, of };
36
+ }
37
+ export function workspaceAddress(sessionId, name, version) {
38
+ const query = new URLSearchParams({ session: sessionId, name });
39
+ if (version)
40
+ query.set('version', String(version));
41
+ return `assistant-workspace?${query.toString()}`;
42
+ }
43
+ /** Sorts by one column - numbers as numbers, empty last, the rest by the reader's collation. */
44
+ export function sortRows(rows, column, direction, locale) {
45
+ const factor = direction === 'asc' ? 1 : -1;
46
+ const collator = new Intl.Collator(locale, { numeric: true });
47
+ return [...rows].sort((a, b) => {
48
+ const x = a[column];
49
+ const y = b[column];
50
+ const emptyX = x === null || x === undefined || x === '';
51
+ const emptyY = y === null || y === undefined || y === '';
52
+ if (emptyX || emptyY)
53
+ return emptyX === emptyY ? 0 : emptyX ? 1 : -1;
54
+ if (typeof x === 'number' && typeof y === 'number')
55
+ return (x - y) * factor;
56
+ return collator.compare(String(x), String(y)) * factor;
57
+ });
58
+ }
59
+ const csvCell = (value) => {
60
+ const text = value === null || value === undefined ? '' : String(value);
61
+ return /[",\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
62
+ };
63
+ /** The table as CSV - the heading row in the column labels, a BOM so a spreadsheet reads Korean. */
64
+ export function tableCsv(columns, rows) {
65
+ const lines = [columns.map(column => csvCell(column.label || column.name)).join(','), ...rows.map(row => columns.map(column => csvCell(row[column.name])).join(','))];
66
+ return '' + lines.join('\r\n');
67
+ }
68
+ //# sourceMappingURL=workspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../client/utils/workspace.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The AI 작업판 on the screen - which versions a workspace has, and the table's sort and CSV\n * (operato-application/design/ai/workspace.md). Pure - tested without a browser.\n */\n\nexport interface WorkspaceBlockView {\n kind: string\n [key: string]: any\n}\n\nexport interface WorkspaceView {\n name: string\n title: string\n summary?: string\n blocks: WorkspaceBlockView[]\n}\n\nexport interface WorkspaceVersion {\n /** 1-based, in the order the conversation made them. */\n version: number\n messageId: string\n createdAt?: string\n workspace: WorkspaceView\n}\n\nexport function isWorkspaceProposal(proposal: any): boolean {\n return proposal?.kind === 'workspace' && !!proposal?.workspace?.name\n}\n\n/**\n * Every version of the named workspace in a conversation, oldest first. A message the reader may not see\n * comes back redacted without its proposals, so a version behind a door is simply not among them.\n */\nexport function workspaceVersions(messages: { id: string; createdAt?: string; proposalsJson?: any[] | null }[], name: string): WorkspaceVersion[] {\n const versions: WorkspaceVersion[] = []\n for (const message of messages) {\n for (const proposal of message.proposalsJson ?? []) {\n if (isWorkspaceProposal(proposal) && proposal.workspace.name === name) {\n versions.push({ version: versions.length + 1, messageId: message.id, createdAt: message.createdAt, workspace: proposal.workspace })\n }\n }\n }\n return versions\n}\n\n/** The version of a card within its conversation - \"2 of 3\" on the card and on the page. */\nexport function versionOfCard(proposalsInOrder: any[][], name: string, key: string): { version: number; of: number } {\n let version = 0\n let of = 0\n for (const list of proposalsInOrder)\n for (const one of list ?? []) {\n if (!isWorkspaceProposal(one) || one.workspace.name !== name) continue\n of += 1\n if (one.key === key) version = of\n }\n return { version, of }\n}\n\nexport function workspaceAddress(sessionId: string, name: string, version?: number): string {\n const query = new URLSearchParams({ session: sessionId, name })\n if (version) query.set('version', String(version))\n return `assistant-workspace?${query.toString()}`\n}\n\n/** Sorts by one column - numbers as numbers, empty last, the rest by the reader's collation. */\nexport function sortRows(rows: Record<string, any>[], column: string, direction: 'asc' | 'desc', locale?: string): Record<string, any>[] {\n const factor = direction === 'asc' ? 1 : -1\n const collator = new Intl.Collator(locale, { numeric: true })\n return [...rows].sort((a, b) => {\n const x = a[column]\n const y = b[column]\n const emptyX = x === null || x === undefined || x === ''\n const emptyY = y === null || y === undefined || y === ''\n if (emptyX || emptyY) return emptyX === emptyY ? 0 : emptyX ? 1 : -1\n if (typeof x === 'number' && typeof y === 'number') return (x - y) * factor\n return collator.compare(String(x), String(y)) * factor\n })\n}\n\nconst csvCell = (value: unknown) => {\n const text = value === null || value === undefined ? '' : String(value)\n return /[\",\\n\\r]/.test(text) ? `\"${text.replace(/\"/g, '\"\"')}\"` : text\n}\n\n/** The table as CSV - the heading row in the column labels, a BOM so a spreadsheet reads Korean. */\nexport function tableCsv(columns: { name: string; label?: string }[], rows: Record<string, any>[]): string {\n const lines = [columns.map(column => csvCell(column.label || column.name)).join(','), ...rows.map(row => columns.map(column => csvCell(row[column.name])).join(','))]\n return '' + lines.join('\\r\\n')\n}\n"]}
@@ -15,8 +15,8 @@ export interface ModeSettings {
15
15
  maxTokens?: number;
16
16
  }
17
17
  /**
18
- * The categories a mode opens: its own, the attachment reader when it takes files (design/ai/shared-dock.md
19
- * §9-4), and one propose tool per record it may enter (design/ai/data-entry.md).
18
+ * The categories a mode opens: its own, the AI 작업판 (design/ai/workspace.md), the attachment reader when it
19
+ * takes files (design/ai/shared-dock.md §9-4), and one propose tool per record it may enter (design/ai/data-entry.md).
20
20
  */
21
21
  export declare function modeToolCategories(mode: {
22
22
  toolCategories?: string[];
@@ -25,15 +25,17 @@ function unknownModeError(kind, t) {
25
25
  return error;
26
26
  }
27
27
  /**
28
- * The categories a mode opens: its own, the attachment reader when it takes files (design/ai/shared-dock.md
29
- * §9-4), and one propose tool per record it may enter (design/ai/data-entry.md).
28
+ * The categories a mode opens: its own, the AI 작업판 (design/ai/workspace.md), the attachment reader when it
29
+ * takes files (design/ai/shared-dock.md §9-4), and one propose tool per record it may enter (design/ai/data-entry.md).
30
30
  */
31
31
  function modeToolCategories(mode) {
32
32
  const extra = [
33
+ /* Every conversation may put a large result on the AI 작업판 (design/ai/workspace.md). */
34
+ 'assistant-workspace',
33
35
  ...(mode.attachments?.kinds?.length ? ['chat-attachments'] : []),
34
36
  ...(mode.entryTargets ?? []).map(key => `data-entry:${key}`)
35
37
  ];
36
- return extra.length ? [...(mode.toolCategories ?? []), ...extra] : mode.toolCategories;
38
+ return [...new Set([...(mode.toolCategories ?? []), ...extra])];
37
39
  }
38
40
  /** The conversation's settings, from the server's declaration. */
39
41
  function resolveModeSettings(ref, t) {
@@ -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 model?: string\n temperature?: number\n maxTokens?: number\n}\n\n/**\n * The categories a mode opens: its own, the attachment reader when it takes files (design/ai/shared-dock.md\n * §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[] }): string[] | undefined {\n const extra = [\n ...(mode.attachments?.kinds?.length ? ['chat-attachments'] : []),\n ...(mode.entryTargets ?? []).map(key => `data-entry:${key}`)\n ]\n return extra.length ? [...(mode.toolCategories ?? []), ...extra] : mode.toolCategories\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 model: mode.model,\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 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 model?: string\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[] }): 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 ]\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 model: mode.model,\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"]}
@@ -8,6 +8,7 @@ import './chat-session/attachment-tools.js';
8
8
  import './chat-session/session-purge.js';
9
9
  import './chat-session/attachment-route.js';
10
10
  import { EntryProposalResolver } from './data-entry/entry-resolver.js';
11
+ import './workspace/workspace-tools.js';
11
12
  export * from './types.js';
12
13
  export * from './chat-session-resolver.js';
13
14
  export * from './assistant-chat-resolver.js';
@@ -17,6 +18,7 @@ export * from './assistant-capability-resolver.js';
17
18
  export * from './tool-access.js';
18
19
  export * from './data-entry/entry-target.js';
19
20
  export { registerEntryTarget, entryCategoryOf } from './data-entry/entry-tools.js';
21
+ export * from './workspace/workspace-blocks.js';
20
22
  export * from './grounding-sources.js';
21
23
  export * from './chat-session/session-anchor.js';
22
24
  export * from './chat-session/session-visibility.js';
@@ -15,6 +15,7 @@ require("./chat-session/attachment-tools.js");
15
15
  require("./chat-session/session-purge.js");
16
16
  require("./chat-session/attachment-route.js");
17
17
  const entry_resolver_js_1 = require("./data-entry/entry-resolver.js");
18
+ require("./workspace/workspace-tools.js");
18
19
  tslib_1.__exportStar(require("./types.js"), exports);
19
20
  tslib_1.__exportStar(require("./chat-session-resolver.js"), exports);
20
21
  tslib_1.__exportStar(require("./assistant-chat-resolver.js"), exports);
@@ -33,6 +34,8 @@ tslib_1.__exportStar(require("./data-entry/entry-target.js"), exports);
33
34
  var entry_tools_js_1 = require("./data-entry/entry-tools.js");
34
35
  Object.defineProperty(exports, "registerEntryTarget", { enumerable: true, get: function () { return entry_tools_js_1.registerEntryTarget; } });
35
36
  Object.defineProperty(exports, "entryCategoryOf", { enumerable: true, get: function () { return entry_tools_js_1.entryCategoryOf; } });
37
+ /* The AI 작업판 - large results on a page of their own (design/ai/workspace.md). */
38
+ tslib_1.__exportStar(require("./workspace/workspace-blocks.js"), exports);
36
39
  /* 무엇이 근거인가 — 도구 결과 밖의 셋(프롬프트 · 대화 · 화면 스냅숏). */
37
40
  tslib_1.__exportStar(require("./grounding-sources.js"), exports);
38
41
  /* 앵커 순수 로직 — 호스트가 앵커 종류를 참조한다(리졸버는 데코레이터 때문에 무겁다). */
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yEAAgE;AAChE,6EAAoE;AACpE,4FAAmF;AACnF,yFAAgF;AAChF,sGAA+F;AAC/F,8FAAqF;AACrF,sDAAyE;AACzE,sDAAyE;AACzE,kEAAgG;AAChG,8CAA2C;AAC3C,2CAAwC;AACxC,8CAA2C;AAC3C,sEAAsE;AAEtE,qDAA0B;AAC1B,qEAA0C;AAC1C,uEAA4C;AAC5C,oGAAoG;AACpG,oEAAyC;AACzC,iEAA2D;AAAlD,0HAAA,iBAAiB,OAAA;AAC1B,6EAAkD;AAClD;;;GAGG;AACH,2DAAgC;AAChC,+GAA+G;AAC/G,uEAA4C;AAC5C,8DAAkF;AAAzE,qHAAA,mBAAmB,OAAA;AAAE,iHAAA,eAAe,OAAA;AAC7C,gDAAgD;AAChD,iEAAsC;AACtC,sDAAsD;AACtD,2EAAgD;AAChD,gHAAgH;AAChH,+EAAoD;AACpD,kEAAuC;AACvC,8DAA8D;AAC9D,0EAA+C;AAC/C,8EAAmD;AACnD,0EAA+C;AAC/C,qFAA0D;AAC1D,uFAA4D;AAC5D,wEAA6C;AAC7C,4EAAiD;AACjD,yEAA8C;AAC9C,wDAAwD;AACxD,iFAAsD;AACtD,iFAAsD;AACtD,mFAAwD;AACxD,4FAAiE;AACjE,2DAA2D;AAC3D,uFAA4D;AAC5D,iFAAsD;AACtD,kEAAuC;AACvC,8EAAmD;AAEtC,QAAA,QAAQ,GAAG,CAAC,GAAG,mBAAmB,EAAE,GAAG,mBAAmB,EAAE,GAAG,mBAA8B,CAAC,CAAA;AAE9F,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,8CAAmB;QACnB,sDAAsD;QACtD,kDAAqB;QACrB,uDAAuD;QACvD,kDAAqB;QACrB,wDAAwD;QACxD,8DAA2B;QAC3B,sDAAuB;QACvB,qDAAqD;QACrD,gEAA6B;QAC7B,mDAAmD;QACnD,yCAAqB;KACtB;CACF,CAAA","sourcesContent":["import { ChatSessionResolver } from './chat-session-resolver.js'\nimport { AssistantChatResolver } from './assistant-chat-resolver.js'\nimport { AssistantModeResolver } from './assistant-mode/assistant-mode-resolver.js'\nimport { AssistantCapabilityResolver } from './assistant-capability-resolver.js'\nimport { MySessionActivitySubscription } from './chat-session/session-activity-subscription.js'\nimport { ChatMessageSubscription } from './chat-message/chat-message-subscription.js'\nimport { entities as ChatSessionEntities } from './chat-session/index.js'\nimport { entities as ChatMessageEntities } from './chat-message/index.js'\nimport { entities as ChatSessionParticipantEntities } from './chat-session-participant/index.js'\nimport './chat-session/attachment-tools.js'\nimport './chat-session/session-purge.js'\nimport './chat-session/attachment-route.js'\nimport { EntryProposalResolver } from './data-entry/entry-resolver.js'\n\nexport * from './types.js'\nexport * from './chat-session-resolver.js'\nexport * from './assistant-chat-resolver.js'\n/* The mode registry — each app server declares what its conversations are (ADR-0061 decision 7). */\nexport * from './assistant-mode/index.js'\nexport { requestTranslator } from './request-translator.js'\nexport * from './assistant-capability-resolver.js'\n/*\n * 대화 각본 하네스는 **런타임 export 에서 뺀다**(ADR-0070 A15 결정 ④) — 시험 도구가 서버 번들에\n * 실린다. 시험은 경로로 가져온다: `…/service/testing/conversation-script.js`.\n */\nexport * from './tool-access.js'\n/* Data entry from material - an app registers what it can enter; a mode names it (design/ai/data-entry.md). */\nexport * from './data-entry/entry-target.js'\nexport { registerEntryTarget, entryCategoryOf } from './data-entry/entry-tools.js'\n/* 무엇이 근거인가 — 도구 결과 밖의 셋(프롬프트 · 대화 · 화면 스냅숏). */\nexport * from './grounding-sources.js'\n/* 앵커 순수 로직 — 호스트가 앵커 종류를 참조한다(리졸버는 데코레이터 때문에 무겁다). */\nexport * from './chat-session/session-anchor.js'\n/* Who may read a conversation — one rule, also asked by doors outside this package (board-ai's revertPatch). */\nexport * from './chat-session/session-visibility.js'\nexport * from './chat-session/index.js'\n/* 순수 로직들 — 호스트(board-ai 등)가 같은 규칙을 쓰라고 공개한다. 베끼면 한쪽만 고쳐진다. */\nexport * from './chat-session/session-inbox.js'\nexport * from './chat-session/attachment-policy.js'\nexport * from './chat-session/session-purge.js'\nexport * from './chat-session/session-activity-publish.js'\nexport * from './chat-session/session-activity-broadcast.js'\nexport * from './chat-message/llm-history.js'\nexport * from './chat-message/history-summary.js'\nexport * from './chat-message/fold-history.js'\n/* 상한 밖으로 밀려난 앞부분을 요약으로 접는 orchestration — 중립 문이 부른다. */\nexport * from './chat-message/fold-session-history.js'\nexport * from './chat-message/chat-message-publish.js'\nexport * from './chat-message/chat-message-broadcast.js'\nexport * from './chat-session-participant/invite-notification.js'\n/* 멘션이 사람을 불러들이는 자리 — 사람의 말을 저장하는 문만 부른다(ADR-0071 결정 1). */\nexport * from './chat-session-participant/mention-invite.js'\nexport * from './chat-session-participant/presence.js'\nexport * from './chat-message/index.js'\nexport * from './chat-session-participant/index.js'\n\nexport const entities = [...ChatSessionEntities, ...ChatMessageEntities, ...ChatSessionParticipantEntities]\n\nexport const schema = {\n resolverClasses: [\n ChatSessionResolver,\n /* 문서를 갖지 않은 표면의 대화 — board 를 안 싣는 제품이 물어볼 수 있는 자리. */\n AssistantChatResolver,\n /* 화면이 자기 대화가 무엇을 주는지 묻는 자리 — 문구는 서버가 요청 로케일로 옮겨 낸다. */\n AssistantModeResolver,\n /* 「이 배포에 AI 가 있나 · 무엇이 선언 없이 등록됐나」 — 창구 하나(표준 A7-a). */\n AssistantCapabilityResolver,\n ChatMessageSubscription,\n /* 내가 참여한 모든 세션의 활동 — 창을 닫아 둔 표면이 배지·목록을 갱신할 수 있게. */\n MySessionActivitySubscription,\n /* 사람이 누른 입력 제안을 쓰는 자리 — 행마다 앱의 코드로, 그 사람의 권한으로. */\n EntryProposalResolver\n ]\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yEAAgE;AAChE,6EAAoE;AACpE,4FAAmF;AACnF,yFAAgF;AAChF,sGAA+F;AAC/F,8FAAqF;AACrF,sDAAyE;AACzE,sDAAyE;AACzE,kEAAgG;AAChG,8CAA2C;AAC3C,2CAAwC;AACxC,8CAA2C;AAC3C,sEAAsE;AACtE,0CAAuC;AAEvC,qDAA0B;AAC1B,qEAA0C;AAC1C,uEAA4C;AAC5C,oGAAoG;AACpG,oEAAyC;AACzC,iEAA2D;AAAlD,0HAAA,iBAAiB,OAAA;AAC1B,6EAAkD;AAClD;;;GAGG;AACH,2DAAgC;AAChC,+GAA+G;AAC/G,uEAA4C;AAC5C,8DAAkF;AAAzE,qHAAA,mBAAmB,OAAA;AAAE,iHAAA,eAAe,OAAA;AAC7C,iFAAiF;AACjF,0EAA+C;AAC/C,gDAAgD;AAChD,iEAAsC;AACtC,sDAAsD;AACtD,2EAAgD;AAChD,gHAAgH;AAChH,+EAAoD;AACpD,kEAAuC;AACvC,8DAA8D;AAC9D,0EAA+C;AAC/C,8EAAmD;AACnD,0EAA+C;AAC/C,qFAA0D;AAC1D,uFAA4D;AAC5D,wEAA6C;AAC7C,4EAAiD;AACjD,yEAA8C;AAC9C,wDAAwD;AACxD,iFAAsD;AACtD,iFAAsD;AACtD,mFAAwD;AACxD,4FAAiE;AACjE,2DAA2D;AAC3D,uFAA4D;AAC5D,iFAAsD;AACtD,kEAAuC;AACvC,8EAAmD;AAEtC,QAAA,QAAQ,GAAG,CAAC,GAAG,mBAAmB,EAAE,GAAG,mBAAmB,EAAE,GAAG,mBAA8B,CAAC,CAAA;AAE9F,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,8CAAmB;QACnB,sDAAsD;QACtD,kDAAqB;QACrB,uDAAuD;QACvD,kDAAqB;QACrB,wDAAwD;QACxD,8DAA2B;QAC3B,sDAAuB;QACvB,qDAAqD;QACrD,gEAA6B;QAC7B,mDAAmD;QACnD,yCAAqB;KACtB;CACF,CAAA","sourcesContent":["import { ChatSessionResolver } from './chat-session-resolver.js'\nimport { AssistantChatResolver } from './assistant-chat-resolver.js'\nimport { AssistantModeResolver } from './assistant-mode/assistant-mode-resolver.js'\nimport { AssistantCapabilityResolver } from './assistant-capability-resolver.js'\nimport { MySessionActivitySubscription } from './chat-session/session-activity-subscription.js'\nimport { ChatMessageSubscription } from './chat-message/chat-message-subscription.js'\nimport { entities as ChatSessionEntities } from './chat-session/index.js'\nimport { entities as ChatMessageEntities } from './chat-message/index.js'\nimport { entities as ChatSessionParticipantEntities } from './chat-session-participant/index.js'\nimport './chat-session/attachment-tools.js'\nimport './chat-session/session-purge.js'\nimport './chat-session/attachment-route.js'\nimport { EntryProposalResolver } from './data-entry/entry-resolver.js'\nimport './workspace/workspace-tools.js'\n\nexport * from './types.js'\nexport * from './chat-session-resolver.js'\nexport * from './assistant-chat-resolver.js'\n/* The mode registry — each app server declares what its conversations are (ADR-0061 decision 7). */\nexport * from './assistant-mode/index.js'\nexport { requestTranslator } from './request-translator.js'\nexport * from './assistant-capability-resolver.js'\n/*\n * 대화 각본 하네스는 **런타임 export 에서 뺀다**(ADR-0070 A15 결정 ④) — 시험 도구가 서버 번들에\n * 실린다. 시험은 경로로 가져온다: `…/service/testing/conversation-script.js`.\n */\nexport * from './tool-access.js'\n/* Data entry from material - an app registers what it can enter; a mode names it (design/ai/data-entry.md). */\nexport * from './data-entry/entry-target.js'\nexport { registerEntryTarget, entryCategoryOf } from './data-entry/entry-tools.js'\n/* The AI 작업판 - large results on a page of their own (design/ai/workspace.md). */\nexport * from './workspace/workspace-blocks.js'\n/* 무엇이 근거인가 — 도구 결과 밖의 셋(프롬프트 · 대화 · 화면 스냅숏). */\nexport * from './grounding-sources.js'\n/* 앵커 순수 로직 — 호스트가 앵커 종류를 참조한다(리졸버는 데코레이터 때문에 무겁다). */\nexport * from './chat-session/session-anchor.js'\n/* Who may read a conversation — one rule, also asked by doors outside this package (board-ai's revertPatch). */\nexport * from './chat-session/session-visibility.js'\nexport * from './chat-session/index.js'\n/* 순수 로직들 — 호스트(board-ai 등)가 같은 규칙을 쓰라고 공개한다. 베끼면 한쪽만 고쳐진다. */\nexport * from './chat-session/session-inbox.js'\nexport * from './chat-session/attachment-policy.js'\nexport * from './chat-session/session-purge.js'\nexport * from './chat-session/session-activity-publish.js'\nexport * from './chat-session/session-activity-broadcast.js'\nexport * from './chat-message/llm-history.js'\nexport * from './chat-message/history-summary.js'\nexport * from './chat-message/fold-history.js'\n/* 상한 밖으로 밀려난 앞부분을 요약으로 접는 orchestration — 중립 문이 부른다. */\nexport * from './chat-message/fold-session-history.js'\nexport * from './chat-message/chat-message-publish.js'\nexport * from './chat-message/chat-message-broadcast.js'\nexport * from './chat-session-participant/invite-notification.js'\n/* 멘션이 사람을 불러들이는 자리 — 사람의 말을 저장하는 문만 부른다(ADR-0071 결정 1). */\nexport * from './chat-session-participant/mention-invite.js'\nexport * from './chat-session-participant/presence.js'\nexport * from './chat-message/index.js'\nexport * from './chat-session-participant/index.js'\n\nexport const entities = [...ChatSessionEntities, ...ChatMessageEntities, ...ChatSessionParticipantEntities]\n\nexport const schema = {\n resolverClasses: [\n ChatSessionResolver,\n /* 문서를 갖지 않은 표면의 대화 — board 를 안 싣는 제품이 물어볼 수 있는 자리. */\n AssistantChatResolver,\n /* 화면이 자기 대화가 무엇을 주는지 묻는 자리 — 문구는 서버가 요청 로케일로 옮겨 낸다. */\n AssistantModeResolver,\n /* 「이 배포에 AI 가 있나 · 무엇이 선언 없이 등록됐나」 — 창구 하나(표준 A7-a). */\n AssistantCapabilityResolver,\n ChatMessageSubscription,\n /* 내가 참여한 모든 세션의 활동 — 창을 닫아 둔 표면이 배지·목록을 갱신할 수 있게. */\n MySessionActivitySubscription,\n /* 사람이 누른 입력 제안을 쓰는 자리 — 행마다 앱의 코드로, 그 사람의 권한으로. */\n EntryProposalResolver\n ]\n}\n"]}
@@ -0,0 +1,24 @@
1
+ export declare const WORKSPACE_BLOCK_KINDS: readonly ["heading", "text", "metrics", "table", "chart", "steps"];
2
+ export type WorkspaceBlockKind = (typeof WORKSPACE_BLOCK_KINDS)[number];
3
+ export declare const MAX_BLOCKS = 40;
4
+ export declare const MAX_TABLE_ROWS = 1000;
5
+ export declare const MAX_SERIES = 8;
6
+ export interface WorkspaceBlock {
7
+ kind: WorkspaceBlockKind | 'unreadable';
8
+ [key: string]: unknown;
9
+ }
10
+ export interface Workspace {
11
+ name: string;
12
+ title: string;
13
+ summary?: string;
14
+ blocks: WorkspaceBlock[];
15
+ }
16
+ /** A short key of the workspace - the same name in one conversation is the same workspace, a new version. */
17
+ export declare function workspaceName(name: unknown, title: string): string;
18
+ /** The workspace as the screen reads it. */
19
+ export declare function normalizeWorkspace(args: any): {
20
+ workspace?: Workspace;
21
+ error?: string;
22
+ };
23
+ /** One line of what is in it - for the card in the conversation and for the model. */
24
+ export declare function workspaceContents(workspace: Workspace): Record<string, number>;
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ /*
3
+ * The AI 작업판 - what a workspace is made of (operato-application/design/ai/workspace.md §3).
4
+ *
5
+ * The model does not write HTML. A workspace is a list of blocks of known kinds; the screen draws each, so
6
+ * theme, language and accessibility are the framework's. A block that does not fit is not dropped: it is
7
+ * kept as a fact in its place ("a block that cannot be drawn"), so the person sees that something was meant
8
+ * to be there. Pure - tested without a server.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.MAX_SERIES = exports.MAX_TABLE_ROWS = exports.MAX_BLOCKS = exports.WORKSPACE_BLOCK_KINDS = void 0;
12
+ exports.workspaceName = workspaceName;
13
+ exports.normalizeWorkspace = normalizeWorkspace;
14
+ exports.workspaceContents = workspaceContents;
15
+ exports.WORKSPACE_BLOCK_KINDS = ['heading', 'text', 'metrics', 'table', 'chart', 'steps'];
16
+ exports.MAX_BLOCKS = 40;
17
+ exports.MAX_TABLE_ROWS = 1000;
18
+ exports.MAX_SERIES = 8;
19
+ const MAX_TEXT = 20_000;
20
+ const text = (value, max = 500) => (typeof value === 'string' && value.trim() ? value.trim().slice(0, max) : undefined);
21
+ const scalar = (value) => value === null || value === undefined ? null : typeof value === 'number' || typeof value === 'boolean' ? value : String(value).slice(0, 500);
22
+ /** A short key of the workspace - the same name in one conversation is the same workspace, a new version. */
23
+ function workspaceName(name, title) {
24
+ const given = typeof name === 'string' ? name.trim().toLowerCase() : '';
25
+ if (/^[a-z0-9][a-z0-9-]{0,63}$/.test(given))
26
+ return given;
27
+ const slug = (given || title)
28
+ .toLowerCase()
29
+ .replace(/[^a-z0-9가-힣]+/g, '-')
30
+ .replace(/^-+|-+$/g, '')
31
+ .slice(0, 64);
32
+ return slug || 'workspace';
33
+ }
34
+ function unreadable(kind, reason) {
35
+ return { kind: 'unreadable', was: typeof kind === 'string' ? kind.slice(0, 40) : null, reason };
36
+ }
37
+ function block(raw) {
38
+ const kind = raw?.kind;
39
+ switch (kind) {
40
+ case 'heading': {
41
+ const value = text(raw.text, 200);
42
+ if (!value)
43
+ return unreadable(kind, 'no-text');
44
+ const level = [1, 2, 3].includes(raw.level) ? raw.level : 2;
45
+ return { kind, text: value, level };
46
+ }
47
+ case 'text': {
48
+ const value = text(raw.markdown ?? raw.text, MAX_TEXT);
49
+ return value ? { kind, markdown: value } : unreadable(kind, 'no-text');
50
+ }
51
+ case 'metrics': {
52
+ const items = (Array.isArray(raw.items) ? raw.items : [])
53
+ .filter((one) => text(one?.label) && one?.value !== undefined && one?.value !== null)
54
+ .slice(0, 12)
55
+ .map((one) => ({ label: text(one.label, 80), value: scalar(one.value), ...(text(one.unit, 20) ? { unit: text(one.unit, 20) } : {}), ...(text(one.note, 200) ? { note: text(one.note, 200) } : {}) }));
56
+ return items.length ? { kind, items } : unreadable(kind, 'no-items');
57
+ }
58
+ case 'table': {
59
+ const columns = (Array.isArray(raw.columns) ? raw.columns : [])
60
+ .filter((one) => text(one?.name, 80))
61
+ .slice(0, 30)
62
+ .map((one) => ({ name: text(one.name, 80), ...(text(one.label, 80) ? { label: text(one.label, 80) } : {}), type: ['number', 'date', 'string'].includes(one.type) ? one.type : 'string' }));
63
+ if (!columns.length)
64
+ return unreadable(kind, 'no-columns');
65
+ const given = Array.isArray(raw.rows) ? raw.rows : [];
66
+ /* A row is an object by column name, or an array in column order - models write both. */
67
+ const rows = given.slice(0, exports.MAX_TABLE_ROWS).map((row) => Object.fromEntries(columns.map((column, i) => [column.name, scalar(Array.isArray(row) ? row[i] : row?.[column.name])])));
68
+ return { kind, columns, rows, ...(text(raw.caption, 300) ? { caption: text(raw.caption, 300) } : {}), ...(given.length > exports.MAX_TABLE_ROWS ? { cut: given.length - exports.MAX_TABLE_ROWS } : {}) };
69
+ }
70
+ case 'chart': {
71
+ const x = (Array.isArray(raw.x) ? raw.x : []).slice(0, 200).map((one) => String(one ?? '').slice(0, 60));
72
+ const series = (Array.isArray(raw.series) ? raw.series : [])
73
+ .filter((one) => text(one?.name, 60) && Array.isArray(one?.values))
74
+ .map((one) => ({ name: text(one.name, 60), values: x.map((_, i) => (typeof one.values[i] === 'number' && Number.isFinite(one.values[i]) ? one.values[i] : null)) }));
75
+ if (!x.length || !series.length)
76
+ return unreadable(kind, 'no-series');
77
+ /* Never a ninth hue: past eight, the first seven stay and the rest fold into one "other". */
78
+ const shown = series.length > exports.MAX_SERIES
79
+ ? [
80
+ ...series.slice(0, exports.MAX_SERIES - 1),
81
+ { name: 'other', other: true, values: x.map((_, i) => series.slice(exports.MAX_SERIES - 1).reduce((acc, one) => acc + (one.values[i] ?? 0), 0)) }
82
+ ]
83
+ : series;
84
+ return { kind, chart: raw.chart === 'line' || raw.type === 'line' || raw.shape === 'line' ? 'line' : 'bar', x, series: shown, ...(text(raw.unit, 20) ? { unit: text(raw.unit, 20) } : {}), ...(text(raw.caption, 300) ? { caption: text(raw.caption, 300) } : {}) };
85
+ }
86
+ case 'steps': {
87
+ const items = (Array.isArray(raw.items) ? raw.items : [])
88
+ .filter((one) => text(one?.label, 200))
89
+ .slice(0, 50)
90
+ .map((one) => ({ label: text(one.label, 200), state: ['done', 'doing', 'todo', 'failed'].includes(one.state) ? one.state : 'todo', ...(text(one.note, 300) ? { note: text(one.note, 300) } : {}) }));
91
+ return items.length ? { kind, items } : unreadable(kind, 'no-items');
92
+ }
93
+ default:
94
+ return unreadable(kind, 'unknown-kind');
95
+ }
96
+ }
97
+ /** The workspace as the screen reads it. */
98
+ function normalizeWorkspace(args) {
99
+ const title = text(args?.title, 200);
100
+ if (!title)
101
+ return { error: 'no-title' };
102
+ const given = Array.isArray(args?.blocks) ? args.blocks : [];
103
+ if (!given.length)
104
+ return { error: 'no-blocks' };
105
+ const blocks = given.slice(0, exports.MAX_BLOCKS).map(block);
106
+ if (given.length > exports.MAX_BLOCKS)
107
+ blocks.push({ kind: 'unreadable', was: null, reason: 'too-many-blocks', cut: given.length - exports.MAX_BLOCKS });
108
+ return { workspace: { name: workspaceName(args?.name, title), title, ...(text(args?.summary, 500) ? { summary: text(args.summary, 500) } : {}), blocks } };
109
+ }
110
+ /** One line of what is in it - for the card in the conversation and for the model. */
111
+ function workspaceContents(workspace) {
112
+ const counts = {};
113
+ for (const one of workspace.blocks)
114
+ if (['table', 'chart', 'metrics', 'steps'].includes(one.kind))
115
+ counts[one.kind] = (counts[one.kind] ?? 0) + 1;
116
+ return counts;
117
+ }
118
+ //# sourceMappingURL=workspace-blocks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-blocks.js","sourceRoot":"","sources":["../../../server/service/workspace/workspace-blocks.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The AI 작업판 - what a workspace is made of (operato-application/design/ai/workspace.md §3).\n *\n * The model does not write HTML. A workspace is a list of blocks of known kinds; the screen draws each, so\n * theme, language and accessibility are the framework's. A block that does not fit is not dropped: it is\n * kept as a fact in its place (\"a block that cannot be drawn\"), so the person sees that something was meant\n * to be there. Pure - tested without a server.\n */\n\nexport const WORKSPACE_BLOCK_KINDS = ['heading', 'text', 'metrics', 'table', 'chart', 'steps'] as const\nexport type WorkspaceBlockKind = (typeof WORKSPACE_BLOCK_KINDS)[number]\n\nexport const MAX_BLOCKS = 40\nexport const MAX_TABLE_ROWS = 1000\nexport const MAX_SERIES = 8\nconst MAX_TEXT = 20_000\n\nexport interface WorkspaceBlock {\n kind: WorkspaceBlockKind | 'unreadable'\n [key: string]: unknown\n}\n\nexport interface Workspace {\n name: string\n title: string\n summary?: string\n blocks: WorkspaceBlock[]\n}\n\nconst text = (value: unknown, max = 500): string | undefined => (typeof value === 'string' && value.trim() ? value.trim().slice(0, max) : undefined)\nconst scalar = (value: unknown): string | number | boolean | null =>\n value === null || value === undefined ? null : typeof value === 'number' || typeof value === 'boolean' ? value : String(value).slice(0, 500)\n\n/** A short key of the workspace - the same name in one conversation is the same workspace, a new version. */\nexport function workspaceName(name: unknown, title: string): string {\n const given = typeof name === 'string' ? name.trim().toLowerCase() : ''\n if (/^[a-z0-9][a-z0-9-]{0,63}$/.test(given)) return given\n const slug = (given || title)\n .toLowerCase()\n .replace(/[^a-z0-9가-힣]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 64)\n return slug || 'workspace'\n}\n\nfunction unreadable(kind: unknown, reason: string): WorkspaceBlock {\n return { kind: 'unreadable', was: typeof kind === 'string' ? kind.slice(0, 40) : null, reason }\n}\n\nfunction block(raw: any): WorkspaceBlock {\n const kind = raw?.kind\n switch (kind) {\n case 'heading': {\n const value = text(raw.text, 200)\n if (!value) return unreadable(kind, 'no-text')\n const level = [1, 2, 3].includes(raw.level) ? raw.level : 2\n return { kind, text: value, level }\n }\n case 'text': {\n const value = text(raw.markdown ?? raw.text, MAX_TEXT)\n return value ? { kind, markdown: value } : unreadable(kind, 'no-text')\n }\n case 'metrics': {\n const items = (Array.isArray(raw.items) ? raw.items : [])\n .filter((one: any) => text(one?.label) && one?.value !== undefined && one?.value !== null)\n .slice(0, 12)\n .map((one: any) => ({ label: text(one.label, 80), value: scalar(one.value), ...(text(one.unit, 20) ? { unit: text(one.unit, 20) } : {}), ...(text(one.note, 200) ? { note: text(one.note, 200) } : {}) }))\n return items.length ? { kind, items } : unreadable(kind, 'no-items')\n }\n case 'table': {\n const columns = (Array.isArray(raw.columns) ? raw.columns : [])\n .filter((one: any) => text(one?.name, 80))\n .slice(0, 30)\n .map((one: any) => ({ name: text(one.name, 80)!, ...(text(one.label, 80) ? { label: text(one.label, 80) } : {}), type: ['number', 'date', 'string'].includes(one.type) ? one.type : 'string' }))\n if (!columns.length) return unreadable(kind, 'no-columns')\n const given = Array.isArray(raw.rows) ? raw.rows : []\n /* A row is an object by column name, or an array in column order - models write both. */\n const rows = given.slice(0, MAX_TABLE_ROWS).map((row: any) =>\n Object.fromEntries(columns.map((column: any, i: number) => [column.name, scalar(Array.isArray(row) ? row[i] : row?.[column.name])]))\n )\n return { kind, columns, rows, ...(text(raw.caption, 300) ? { caption: text(raw.caption, 300) } : {}), ...(given.length > MAX_TABLE_ROWS ? { cut: given.length - MAX_TABLE_ROWS } : {}) }\n }\n case 'chart': {\n const x = (Array.isArray(raw.x) ? raw.x : []).slice(0, 200).map((one: unknown) => String(one ?? '').slice(0, 60))\n const series = (Array.isArray(raw.series) ? raw.series : [])\n .filter((one: any) => text(one?.name, 60) && Array.isArray(one?.values))\n .map((one: any) => ({ name: text(one.name, 60)!, values: x.map((_: unknown, i: number) => (typeof one.values[i] === 'number' && Number.isFinite(one.values[i]) ? one.values[i] : null)) }))\n if (!x.length || !series.length) return unreadable(kind, 'no-series')\n /* Never a ninth hue: past eight, the first seven stay and the rest fold into one \"other\". */\n const shown =\n series.length > MAX_SERIES\n ? [\n ...series.slice(0, MAX_SERIES - 1),\n { name: 'other', other: true, values: x.map((_: unknown, i: number) => series.slice(MAX_SERIES - 1).reduce((acc: number, one: any) => acc + (one.values[i] ?? 0), 0)) }\n ]\n : series\n return { kind, chart: raw.chart === 'line' || raw.type === 'line' || raw.shape === 'line' ? 'line' : 'bar', x, series: shown, ...(text(raw.unit, 20) ? { unit: text(raw.unit, 20) } : {}), ...(text(raw.caption, 300) ? { caption: text(raw.caption, 300) } : {}) }\n }\n case 'steps': {\n const items = (Array.isArray(raw.items) ? raw.items : [])\n .filter((one: any) => text(one?.label, 200))\n .slice(0, 50)\n .map((one: any) => ({ label: text(one.label, 200), state: ['done', 'doing', 'todo', 'failed'].includes(one.state) ? one.state : 'todo', ...(text(one.note, 300) ? { note: text(one.note, 300) } : {}) }))\n return items.length ? { kind, items } : unreadable(kind, 'no-items')\n }\n default:\n return unreadable(kind, 'unknown-kind')\n }\n}\n\n/** The workspace as the screen reads it. */\nexport function normalizeWorkspace(args: any): { workspace?: Workspace; error?: string } {\n const title = text(args?.title, 200)\n if (!title) return { error: 'no-title' }\n const given = Array.isArray(args?.blocks) ? args.blocks : []\n if (!given.length) return { error: 'no-blocks' }\n const blocks = given.slice(0, MAX_BLOCKS).map(block)\n if (given.length > MAX_BLOCKS) blocks.push({ kind: 'unreadable', was: null, reason: 'too-many-blocks', cut: given.length - MAX_BLOCKS })\n return { workspace: { name: workspaceName(args?.name, title), title, ...(text(args?.summary, 500) ? { summary: text(args.summary, 500) } : {}), blocks } }\n}\n\n/** One line of what is in it - for the card in the conversation and for the model. */\nexport function workspaceContents(workspace: Workspace): Record<string, number> {\n const counts: Record<string, number> = {}\n for (const one of workspace.blocks) if (['table', 'chart', 'metrics', 'steps'].includes(one.kind)) counts[one.kind] = (counts[one.kind] ?? 0) + 1\n return counts\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import { type ToolCategory } from '@things-factory/ai-client-base';
2
+ export declare const WORKSPACE_CATEGORY = "assistant-workspace";
3
+ export declare function showInWorkspace(args: any): {
4
+ rejected: boolean;
5
+ error: string;
6
+ message: string;
7
+ proposed?: undefined;
8
+ kind?: undefined;
9
+ icon?: undefined;
10
+ label?: undefined;
11
+ workspace?: undefined;
12
+ contents?: undefined;
13
+ } | {
14
+ proposed: boolean;
15
+ kind: string;
16
+ icon: string;
17
+ label: string;
18
+ workspace: import("./workspace-blocks.js").Workspace;
19
+ contents: Record<string, number>;
20
+ message: string;
21
+ rejected?: undefined;
22
+ error?: undefined;
23
+ };
24
+ export declare const workspaceCategory: ToolCategory;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.workspaceCategory = exports.WORKSPACE_CATEGORY = void 0;
4
+ exports.showInWorkspace = showInWorkspace;
5
+ /*
6
+ * showInWorkspace - the model puts a large result on the AI 작업판 (operato-application/design/ai/workspace.md §5).
7
+ *
8
+ * The result rides on the answer as a proposal of kind 'workspace', so the conversation's gates hold for it:
9
+ * who may read the conversation reads the workspace, and a value behind a door the reader cannot pass hides
10
+ * it with the answer (ADR-0073). The tool itself reaches no door - what it carries came from the turn's other
11
+ * tools, whose doors the answer row collects.
12
+ */
13
+ const ai_client_base_1 = require("@things-factory/ai-client-base");
14
+ const workspace_blocks_js_1 = require("./workspace-blocks.js");
15
+ exports.WORKSPACE_CATEGORY = 'assistant-workspace';
16
+ function showInWorkspace(args) {
17
+ const { workspace, error } = (0, workspace_blocks_js_1.normalizeWorkspace)(args);
18
+ if (!workspace)
19
+ return { rejected: true, error, message: 'a workspace needs a title and at least one block.' };
20
+ const contents = (0, workspace_blocks_js_1.workspaceContents)(workspace);
21
+ return {
22
+ proposed: true,
23
+ kind: 'workspace',
24
+ icon: 'dashboard',
25
+ label: workspace.title,
26
+ workspace,
27
+ contents,
28
+ message: `put "${workspace.title}" on the workspace (${workspace.blocks.length} blocks). The person opens it from the card - say in one line what it holds; do not repeat its tables in the answer.`
29
+ };
30
+ }
31
+ const spec = {
32
+ kind: 'read',
33
+ name: 'showInWorkspace',
34
+ doors: [],
35
+ description: 'Put a large result on the AI workspace - a page the person opens from a card, beside the conversation. Blocks: ' +
36
+ 'heading{text,level}, text{markdown}, metrics{items:[{label,value,unit,note}]}, table{columns:[{name,label,type:number|date|string}],rows,caption}, ' +
37
+ 'chart{chart:bar|line, x:[labels], series:[{name,values:[numbers]}], unit, caption}, steps{items:[{label,state:done|doing|todo|failed,note}]}.',
38
+ schema: {
39
+ type: 'object',
40
+ properties: {
41
+ name: { type: 'string', description: 'A short key, lowercase with dashes ("contracts-by-customer"). The same name again makes a new version of the same workspace.' },
42
+ title: { type: 'string' },
43
+ summary: { type: 'string', description: 'One line under the title.' },
44
+ blocks: {
45
+ type: 'array',
46
+ items: { type: 'object', properties: { kind: { type: 'string', enum: [...workspace_blocks_js_1.WORKSPACE_BLOCK_KINDS] } }, required: ['kind'] }
47
+ }
48
+ },
49
+ required: ['name', 'title', 'blocks']
50
+ },
51
+ builder: (args) => showInWorkspace(args)
52
+ };
53
+ exports.workspaceCategory = {
54
+ name: exports.WORKSPACE_CATEGORY,
55
+ description: 'the AI workspace - a page for results too large for the conversation',
56
+ guidance: [
57
+ '- When a result is more than a short table (over 20 rows, or two or more tables or charts), or the person asks for a page, call showInWorkspace. Keep the answer short: the point, and that it is on the workspace.',
58
+ '- Every number in a block comes from a tool result in this conversation - never computed or guessed by you.',
59
+ '- To change a workspace, call showInWorkspace again with the same name and the whole new content; it becomes a new version.',
60
+ '- Never say something is on the workspace unless you called showInWorkspace in this turn.'
61
+ ].join('\n'),
62
+ specs: [spec]
63
+ };
64
+ (0, ai_client_base_1.registerToolCategory)(exports.workspaceCategory);
65
+ //# sourceMappingURL=workspace-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-tools.js","sourceRoot":"","sources":["../../../server/service/workspace/workspace-tools.ts"],"names":[],"mappings":";;;AAcA,0CAaC;AA3BD;;;;;;;GAOG;AACH,mEAAuG;AAEvG,+DAAoG;AAEvF,QAAA,kBAAkB,GAAG,qBAAqB,CAAA;AAEvD,SAAgB,eAAe,CAAC,IAAS;IACvC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,IAAA,wCAAkB,EAAC,IAAI,CAAC,CAAA;IACrD,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,mDAAmD,EAAE,CAAA;IAC9G,MAAM,QAAQ,GAAG,IAAA,uCAAiB,EAAC,SAAS,CAAC,CAAA;IAC7C,OAAO;QACL,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,SAAS,CAAC,KAAK;QACtB,SAAS;QACT,QAAQ;QACR,OAAO,EAAE,QAAQ,SAAS,CAAC,KAAK,uBAAuB,SAAS,CAAC,MAAM,CAAC,MAAM,sHAAsH;KACrM,CAAA;AACH,CAAC;AAED,MAAM,IAAI,GAAG;IACX,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,iBAAiB;IACvB,KAAK,EAAE,EAAE;IACT,WAAW,EACT,iHAAiH;QACjH,qJAAqJ;QACrJ,+IAA+I;IACjJ,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,8HAA8H,EAAE;YACrK,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACzB,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,2BAA2B,EAAE;YACrE,MAAM,EAAE;gBACN,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,GAAG,2CAAqB,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE;aAC1H;SACF;QACD,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC;KACtC;IACD,OAAO,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC;CAClC,CAAA;AAEA,QAAA,iBAAiB,GAAiB;IAC7C,IAAI,EAAE,0BAAkB;IACxB,WAAW,EAAE,sEAAsE;IACnF,QAAQ,EAAE;QACR,qNAAqN;QACrN,6GAA6G;QAC7G,6HAA6H;QAC7H,2FAA2F;KAC5F,CAAC,IAAI,CAAC,IAAI,CAAC;IACZ,KAAK,EAAE,CAAC,IAAI,CAAC;CACd,CAAA;AAED,IAAA,qCAAoB,EAAC,yBAAiB,CAAC,CAAA","sourcesContent":["/*\n * showInWorkspace - the model puts a large result on the AI 작업판 (operato-application/design/ai/workspace.md §5).\n *\n * The result rides on the answer as a proposal of kind 'workspace', so the conversation's gates hold for it:\n * who may read the conversation reads the workspace, and a value behind a door the reader cannot pass hides\n * it with the answer (ADR-0073). The tool itself reaches no door - what it carries came from the turn's other\n * tools, whose doors the answer row collects.\n */\nimport { registerToolCategory, type ToolCategory, type ToolSpec } from '@things-factory/ai-client-base'\n\nimport { normalizeWorkspace, workspaceContents, WORKSPACE_BLOCK_KINDS } from './workspace-blocks.js'\n\nexport const WORKSPACE_CATEGORY = 'assistant-workspace'\n\nexport function showInWorkspace(args: any) {\n const { workspace, error } = normalizeWorkspace(args)\n if (!workspace) return { rejected: true, error, message: 'a workspace needs a title and at least one block.' }\n const contents = workspaceContents(workspace)\n return {\n proposed: true,\n kind: 'workspace',\n icon: 'dashboard',\n label: workspace.title,\n workspace,\n contents,\n message: `put \"${workspace.title}\" on the workspace (${workspace.blocks.length} blocks). The person opens it from the card - say in one line what it holds; do not repeat its tables in the answer.`\n }\n}\n\nconst spec = {\n kind: 'read',\n name: 'showInWorkspace',\n doors: [],\n description:\n 'Put a large result on the AI workspace - a page the person opens from a card, beside the conversation. Blocks: ' +\n 'heading{text,level}, text{markdown}, metrics{items:[{label,value,unit,note}]}, table{columns:[{name,label,type:number|date|string}],rows,caption}, ' +\n 'chart{chart:bar|line, x:[labels], series:[{name,values:[numbers]}], unit, caption}, steps{items:[{label,state:done|doing|todo|failed,note}]}.',\n schema: {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'A short key, lowercase with dashes (\"contracts-by-customer\"). The same name again makes a new version of the same workspace.' },\n title: { type: 'string' },\n summary: { type: 'string', description: 'One line under the title.' },\n blocks: {\n type: 'array',\n items: { type: 'object', properties: { kind: { type: 'string', enum: [...WORKSPACE_BLOCK_KINDS] } }, required: ['kind'] }\n }\n },\n required: ['name', 'title', 'blocks']\n },\n builder: (args: any) => showInWorkspace(args)\n} as ToolSpec\n\nexport const workspaceCategory: ToolCategory = {\n name: WORKSPACE_CATEGORY,\n description: 'the AI workspace - a page for results too large for the conversation',\n guidance: [\n '- When a result is more than a short table (over 20 rows, or two or more tables or charts), or the person asks for a page, call showInWorkspace. Keep the answer short: the point, and that it is on the workspace.',\n '- Every number in a block comes from a tool result in this conversation - never computed or guessed by you.',\n '- To change a workspace, call showInWorkspace again with the same name and the whole new content; it becomes a new version.',\n '- Never say something is on the workspace unless you called showInWorkspace in this turn.'\n ].join('\\n'),\n specs: [spec]\n}\n\nregisterToolCategory(workspaceCategory)\n"]}