@things-factory/ai-assistant 10.1.223 → 10.1.227

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 (105) hide show
  1. package/client/components/assistant-chat.ts +50 -2
  2. package/client/components/assistant-dock.ts +1 -1
  3. package/client/components/assistant-mode.ts +1 -1
  4. package/client/components/entry-review.ts +2 -2
  5. package/client/components/workspace-view.ts +1 -1
  6. package/client/index.ts +1 -1
  7. package/client/pages/assistant-workspace-page.ts +1 -1
  8. package/client/route.ts +1 -1
  9. package/client/utils/attachment-policy.ts +15 -8
  10. package/client/utils/document-text.ts +332 -0
  11. package/client/utils/entry-hash.ts +1 -1
  12. package/client/utils/entry-proposal.ts +1 -1
  13. package/client/utils/entry-workspace.ts +1 -1
  14. package/client/utils/pdf-pages.ts +38 -0
  15. package/client/utils/workspace.ts +1 -1
  16. package/dist-client/components/assistant-chat.d.ts +3 -1
  17. package/dist-client/components/assistant-chat.js +48 -2
  18. package/dist-client/components/assistant-chat.js.map +1 -1
  19. package/dist-client/components/assistant-dock.js +1 -1
  20. package/dist-client/components/assistant-dock.js.map +1 -1
  21. package/dist-client/components/assistant-mode.d.ts +1 -1
  22. package/dist-client/components/assistant-mode.js.map +1 -1
  23. package/dist-client/components/entry-review.d.ts +1 -1
  24. package/dist-client/components/entry-review.js +2 -2
  25. package/dist-client/components/entry-review.js.map +1 -1
  26. package/dist-client/components/workspace-view.js +1 -1
  27. package/dist-client/components/workspace-view.js.map +1 -1
  28. package/dist-client/index.js +1 -1
  29. package/dist-client/index.js.map +1 -1
  30. package/dist-client/pages/assistant-workspace-page.js +1 -1
  31. package/dist-client/pages/assistant-workspace-page.js.map +1 -1
  32. package/dist-client/route.js +1 -1
  33. package/dist-client/route.js.map +1 -1
  34. package/dist-client/tsconfig.tsbuildinfo +1 -1
  35. package/dist-client/utils/attachment-policy.d.ts +2 -2
  36. package/dist-client/utils/attachment-policy.js +9 -5
  37. package/dist-client/utils/attachment-policy.js.map +1 -1
  38. package/dist-client/utils/document-text.d.ts +118 -0
  39. package/dist-client/utils/document-text.js +320 -0
  40. package/dist-client/utils/document-text.js.map +1 -0
  41. package/dist-client/utils/entry-hash.js +1 -1
  42. package/dist-client/utils/entry-hash.js.map +1 -1
  43. package/dist-client/utils/entry-proposal.js +1 -1
  44. package/dist-client/utils/entry-proposal.js.map +1 -1
  45. package/dist-client/utils/entry-workspace.js +1 -1
  46. package/dist-client/utils/entry-workspace.js.map +1 -1
  47. package/dist-client/utils/pdf-pages.d.ts +6 -0
  48. package/dist-client/utils/pdf-pages.js +34 -0
  49. package/dist-client/utils/pdf-pages.js.map +1 -0
  50. package/dist-client/utils/workspace.js +1 -1
  51. package/dist-client/utils/workspace.js.map +1 -1
  52. package/dist-server/service/assistant-mode/assistant-mode-resolver.js +1 -1
  53. package/dist-server/service/assistant-mode/assistant-mode-resolver.js.map +1 -1
  54. package/dist-server/service/assistant-mode/mode-categories.d.ts +3 -3
  55. package/dist-server/service/assistant-mode/mode-categories.js +4 -4
  56. package/dist-server/service/assistant-mode/mode-categories.js.map +1 -1
  57. package/dist-server/service/chat-session/attachment-policy.d.ts +1 -1
  58. package/dist-server/service/chat-session/attachment-policy.js +2 -2
  59. package/dist-server/service/chat-session/attachment-policy.js.map +1 -1
  60. package/dist-server/service/data-entry/entry-apply.d.ts +1 -1
  61. package/dist-server/service/data-entry/entry-apply.js +1 -1
  62. package/dist-server/service/data-entry/entry-apply.js.map +1 -1
  63. package/dist-server/service/data-entry/entry-hash.js +1 -1
  64. package/dist-server/service/data-entry/entry-hash.js.map +1 -1
  65. package/dist-server/service/data-read/read-target.d.ts +1 -1
  66. package/dist-server/service/data-read/read-target.js +2 -2
  67. package/dist-server/service/data-read/read-target.js.map +1 -1
  68. package/dist-server/service/data-read/read-tools.js +5 -5
  69. package/dist-server/service/data-read/read-tools.js.map +1 -1
  70. package/dist-server/service/index.js +1 -1
  71. package/dist-server/service/index.js.map +1 -1
  72. package/dist-server/service/people/index.js +1 -1
  73. package/dist-server/service/people/index.js.map +1 -1
  74. package/dist-server/service/workspace/workspace-blocks.js +1 -1
  75. package/dist-server/service/workspace/workspace-blocks.js.map +1 -1
  76. package/dist-server/service/workspace/workspace-tools.js +1 -1
  77. package/dist-server/service/workspace/workspace-tools.js.map +1 -1
  78. package/dist-server/tsconfig.tsbuildinfo +1 -1
  79. package/package.json +11 -9
  80. package/server/service/assistant-mode/assistant-mode-resolver.ts +1 -1
  81. package/server/service/assistant-mode/mode-categories.ts +4 -4
  82. package/server/service/assistant-mode/mode-front.test.ts +1 -1
  83. package/server/service/assistant-mode/resolve.test.ts +1 -1
  84. package/server/service/chat-session/attachment-policy.ts +7 -5
  85. package/server/service/data-entry/entry-apply.ts +2 -2
  86. package/server/service/data-entry/entry-hash.ts +1 -1
  87. package/server/service/data-read/read-target.ts +2 -2
  88. package/server/service/data-read/read-tools.ts +5 -5
  89. package/server/service/data-read/read-words.test.ts +1 -1
  90. package/server/service/index.ts +1 -1
  91. package/server/service/people/index.ts +1 -1
  92. package/server/service/unattended-run/run-unattended.test.ts +1 -1
  93. package/server/service/workspace/workspace-blocks.ts +1 -1
  94. package/server/service/workspace/workspace-tools.ts +1 -1
  95. package/server/service/workspace/workspace.test.ts +1 -1
  96. package/test/attachment-policy-agree.test.ts +4 -1
  97. package/test/document-text.test.ts +175 -0
  98. package/test/entry-workspace.test.ts +1 -1
  99. package/test/workspace.test.ts +1 -1
  100. package/translations/en.json +6 -0
  101. package/translations/es.json +6 -0
  102. package/translations/ja.json +6 -0
  103. package/translations/ko.json +16 -10
  104. package/translations/ms.json +6 -0
  105. package/translations/zh.json +6 -0
@@ -46,7 +46,7 @@ export function entryProposalHandler(apply, t, reviewedOf = reviewedHashOf, repo
46
46
  return async (proposal, context) => {
47
47
  if (!context.sessionId || !proposal?.key)
48
48
  return { verdict: 'unavailable', errorCode: 'no-conversation' };
49
- /* Reviewed in the 작업판: entered only as it was shown there (design/ai/workspace.md §6-1) - here, and again at the server. */
49
+ /* Reviewed in the 작업 패널: entered only as it was shown there (design/ai/workspace.md §6-1) - here, and again at the server. */
50
50
  const reviewed = reviewedOf(proposal.key);
51
51
  if (!reviewStillHolds(proposal, reviewed))
52
52
  return { verdict: 'refused', errorCode: 'review-stale', note: t('ai-assistant.entry.review-stale', { defaultValue: 'this proposal changed after it was reviewed in the workspace - review it again before entering.' }) };
@@ -1 +1 @@
1
- {"version":3,"file":"entry-proposal.js","sourceRoot":"","sources":["../../client/utils/entry-proposal.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The data entry card's rules and its handler (operato-application/design/ai/data-entry.md §5).\n *\n * The card is a review, not an editor: a correction is said in the conversation and the model proposes\n * again - the newer card replaces the older one. Pressing asks the server to enter the stored rows; the\n * screen sends no rows of its own.\n */\nimport type { ProposalContext, ProposalOutcome } from './assistant-proposals.js'\nimport { reviewedHashOf, reviewStillHolds } from './entry-workspace.js'\n\nexport interface EntryIssueView {\n field?: string\n code: string\n messageKey?: string\n params?: Record<string, unknown>\n paramKeys?: Record<string, string>\n blocking: boolean\n}\n\nexport interface EntryRowView {\n index: number\n values: Record<string, unknown>\n sources: { attachmentId?: string; where?: string; person?: boolean }[]\n issues: EntryIssueView[]\n /** The server found this row's numbers and dates together on this line of the cited file. */\n evidence?: { attachmentId: string; line: number }\n}\n\nexport interface EntryTotalView {\n field: string\n all: number\n ready: number\n declared?: number\n declaredFound?: boolean\n difference?: number\n declaredBy?: 'model' | 'file'\n}\n\nexport interface EntryRowResult {\n index: number\n ok: boolean\n id?: string\n label?: string\n error?: string\n}\n\nexport interface EntryApplyAnswer extends ProposalOutcome {\n rows?: EntryRowResult[]\n refusal?: { message: string; extensions: Record<string, unknown>; path: string[] }\n /** Set by the handler from the card - which words the conversation line uses. */\n op?: string\n}\n\nexport function isEntryProposal(proposal: any): boolean {\n return proposal?.kind === 'entry' && Array.isArray(proposal?.rows)\n}\n\nexport function entryReadyCount(proposal: any): number {\n const rows: EntryRowView[] = proposal?.rows ?? []\n const blocked = (row: EntryRowView) => row.issues?.some(issue => issue.blocking)\n if (!proposal?.groupBy) return rows.filter(row => !blocked(row)).length\n /* Lines of one record go in together - a record with a line kept out is kept out whole, and its lines do not count. */\n const keyOf = (row: EntryRowView) => {\n const value: any = row.values?.[proposal.groupBy]\n return value === undefined || value === null || value === '' ? `row-${row.index}` : String(typeof value === 'object' ? value.id ?? value.text : value)\n }\n const out = new Set(rows.filter(blocked).map(keyOf))\n return rows.filter(row => !out.has(keyOf(row))).length\n}\n\n/** The i18n key of an issue - the target's own, or the framework's by code. */\nexport function entryIssueKey(issue: EntryIssueView): string {\n return issue.messageKey || `ai-assistant.entry.issue.${issue.code}`\n}\n\n/** The issues of one cell, and of the row as a whole (no field). */\nexport function entryCellIssues(row: EntryRowView, field?: string): EntryIssueView[] {\n return (row.issues ?? []).filter(issue => (field ? issue.field === field : !issue.field))\n}\n\n/** What stays in the conversation - the model reads it on its next turn, and the person sees what went in. */\nexport function entryOutcomeNote(answer: EntryApplyAnswer, t: (key: string, options?: any) => string): string {\n const refused = (answer.rows ?? []).filter(row => !row.ok)\n const head = answer.op === 'update'\n ? answer.applied\n ? t('ai-assistant.entry.changed', { defaultValue: 'changed {count} records.', count: answer.applied })\n : t('ai-assistant.entry.changed-none', { defaultValue: 'no record was changed.' })\n : answer.applied\n ? t('ai-assistant.entry.entered', { defaultValue: 'entered {count} rows.', count: answer.applied })\n : t('ai-assistant.entry.entered-none', { defaultValue: 'no row was entered.' })\n const lines = refused.slice(0, 20).map(row => t('ai-assistant.entry.row-refused', { defaultValue: 'row {n}: {reason}', n: row.index + 1, reason: row.error || '' }))\n const more = refused.length > 20 ? [t('ai-assistant.entry.more-refused', { defaultValue: '{count} more rows were not entered.', count: refused.length - 20 })] : []\n return [head, ...lines, ...more].join('\\n')\n}\n\n/** The handler every entry card runs - built with the mutation so the rules are tested without a server. */\nexport function entryProposalHandler(\n apply: (sessionId: string, proposalKey: string, reviewedHash?: string) => Promise<EntryApplyAnswer>,\n t: (key: string, options?: any) => string,\n reviewedOf: (key: string) => string | undefined = reviewedHashOf,\n reportRefusal: (notification: { level: 'error'; message: string; ex: NonNullable<EntryApplyAnswer['refusal']>[] }) => void | Promise<void> = async notification => {\n /* The generic toast transport. auth-ui alone interprets the refusal and offers an access request. */\n const { notify } = await import('@operato/layout')\n notify(notification)\n }\n) {\n return async (proposal: any, context: ProposalContext): Promise<ProposalOutcome> => {\n if (!context.sessionId || !proposal?.key) return { verdict: 'unavailable', errorCode: 'no-conversation' }\n /* Reviewed in the 작업판: entered only as it was shown there (design/ai/workspace.md §6-1) - here, and again at the server. */\n const reviewed = reviewedOf(proposal.key)\n if (!reviewStillHolds(proposal, reviewed)) return { verdict: 'refused', errorCode: 'review-stale', note: t('ai-assistant.entry.review-stale', { defaultValue: 'this proposal changed after it was reviewed in the workspace - review it again before entering.' }) } as any\n const answer = { ...(await apply(context.sessionId, proposal.key, reviewed)), op: proposal.op }\n if (answer.verdict === 'refused' && answer.errorCode === 'door-closed' && answer.refusal) {\n try {\n await reportRefusal({ level: 'error', message: answer.refusal.message, ex: [answer.refusal] })\n } catch {\n /* Notification delivery is best-effort; it never changes the stored proposal's refusal. */\n }\n }\n if (answer.errorCode === 'review-stale')\n return { verdict: 'refused', errorCode: 'review-stale', applied: 0, missed: 0, note: t('ai-assistant.entry.review-stale', { defaultValue: 'this proposal changed after it was reviewed in the workspace - review it again before entering.' }) }\n return {\n verdict: answer.verdict,\n ...(answer.errorCode ? { errorCode: answer.errorCode } : {}),\n applied: answer.applied ?? 0,\n missed: answer.missed ?? 0,\n note: entryOutcomeNote(answer, t)\n }\n }\n}\n\n/**\n * The newest card per record, over the conversation's proposals in order - an older one is replaced and\n * cannot be entered (the server refuses it as `superseded`; the card says so before anyone presses).\n */\nexport function latestEntryKeys(proposalsInOrder: any[][]): Map<string, string> {\n const latest = new Map<string, string>()\n for (const list of proposalsInOrder) for (const one of list ?? []) if (isEntryProposal(one) && one.key) latest.set(entryGroupOf(one), one.key)\n return latest\n}\n\n/** A 입력안 and a 수정안 of one record are counted apart - a newer one of the same kind replaces the older. */\nexport function entryGroupOf(proposal: any): string {\n return `${proposal?.target}:${proposal?.op ?? 'create'}`\n}\n\n/**\n * The card's title - the record's name from the target (`부서`, `거래처`) and the kind of card from here, so a\n * 수정안 is never titled as a 입력안.\n */\nexport function entryTitle(proposal: any, t: (key: string, options?: any) => string): string {\n const name = proposal?.labelKey ? t(proposal.labelKey) : proposal?.target ?? ''\n return proposal?.op === 'update'\n ? t('ai-assistant.entry.title-update', { defaultValue: '{name} to change', name })\n : t('ai-assistant.entry.title-create', { defaultValue: '{name} to enter', name })\n}\n\nexport function isChangeProposal(proposal: any): boolean {\n return isEntryProposal(proposal) && proposal.op === 'update'\n}\n"]}
1
+ {"version":3,"file":"entry-proposal.js","sourceRoot":"","sources":["../../client/utils/entry-proposal.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The data entry card's rules and its handler (operato-application/design/ai/data-entry.md §5).\n *\n * The card is a review, not an editor: a correction is said in the conversation and the model proposes\n * again - the newer card replaces the older one. Pressing asks the server to enter the stored rows; the\n * screen sends no rows of its own.\n */\nimport type { ProposalContext, ProposalOutcome } from './assistant-proposals.js'\nimport { reviewedHashOf, reviewStillHolds } from './entry-workspace.js'\n\nexport interface EntryIssueView {\n field?: string\n code: string\n messageKey?: string\n params?: Record<string, unknown>\n paramKeys?: Record<string, string>\n blocking: boolean\n}\n\nexport interface EntryRowView {\n index: number\n values: Record<string, unknown>\n sources: { attachmentId?: string; where?: string; person?: boolean }[]\n issues: EntryIssueView[]\n /** The server found this row's numbers and dates together on this line of the cited file. */\n evidence?: { attachmentId: string; line: number }\n}\n\nexport interface EntryTotalView {\n field: string\n all: number\n ready: number\n declared?: number\n declaredFound?: boolean\n difference?: number\n declaredBy?: 'model' | 'file'\n}\n\nexport interface EntryRowResult {\n index: number\n ok: boolean\n id?: string\n label?: string\n error?: string\n}\n\nexport interface EntryApplyAnswer extends ProposalOutcome {\n rows?: EntryRowResult[]\n refusal?: { message: string; extensions: Record<string, unknown>; path: string[] }\n /** Set by the handler from the card - which words the conversation line uses. */\n op?: string\n}\n\nexport function isEntryProposal(proposal: any): boolean {\n return proposal?.kind === 'entry' && Array.isArray(proposal?.rows)\n}\n\nexport function entryReadyCount(proposal: any): number {\n const rows: EntryRowView[] = proposal?.rows ?? []\n const blocked = (row: EntryRowView) => row.issues?.some(issue => issue.blocking)\n if (!proposal?.groupBy) return rows.filter(row => !blocked(row)).length\n /* Lines of one record go in together - a record with a line kept out is kept out whole, and its lines do not count. */\n const keyOf = (row: EntryRowView) => {\n const value: any = row.values?.[proposal.groupBy]\n return value === undefined || value === null || value === '' ? `row-${row.index}` : String(typeof value === 'object' ? value.id ?? value.text : value)\n }\n const out = new Set(rows.filter(blocked).map(keyOf))\n return rows.filter(row => !out.has(keyOf(row))).length\n}\n\n/** The i18n key of an issue - the target's own, or the framework's by code. */\nexport function entryIssueKey(issue: EntryIssueView): string {\n return issue.messageKey || `ai-assistant.entry.issue.${issue.code}`\n}\n\n/** The issues of one cell, and of the row as a whole (no field). */\nexport function entryCellIssues(row: EntryRowView, field?: string): EntryIssueView[] {\n return (row.issues ?? []).filter(issue => (field ? issue.field === field : !issue.field))\n}\n\n/** What stays in the conversation - the model reads it on its next turn, and the person sees what went in. */\nexport function entryOutcomeNote(answer: EntryApplyAnswer, t: (key: string, options?: any) => string): string {\n const refused = (answer.rows ?? []).filter(row => !row.ok)\n const head = answer.op === 'update'\n ? answer.applied\n ? t('ai-assistant.entry.changed', { defaultValue: 'changed {count} records.', count: answer.applied })\n : t('ai-assistant.entry.changed-none', { defaultValue: 'no record was changed.' })\n : answer.applied\n ? t('ai-assistant.entry.entered', { defaultValue: 'entered {count} rows.', count: answer.applied })\n : t('ai-assistant.entry.entered-none', { defaultValue: 'no row was entered.' })\n const lines = refused.slice(0, 20).map(row => t('ai-assistant.entry.row-refused', { defaultValue: 'row {n}: {reason}', n: row.index + 1, reason: row.error || '' }))\n const more = refused.length > 20 ? [t('ai-assistant.entry.more-refused', { defaultValue: '{count} more rows were not entered.', count: refused.length - 20 })] : []\n return [head, ...lines, ...more].join('\\n')\n}\n\n/** The handler every entry card runs - built with the mutation so the rules are tested without a server. */\nexport function entryProposalHandler(\n apply: (sessionId: string, proposalKey: string, reviewedHash?: string) => Promise<EntryApplyAnswer>,\n t: (key: string, options?: any) => string,\n reviewedOf: (key: string) => string | undefined = reviewedHashOf,\n reportRefusal: (notification: { level: 'error'; message: string; ex: NonNullable<EntryApplyAnswer['refusal']>[] }) => void | Promise<void> = async notification => {\n /* The generic toast transport. auth-ui alone interprets the refusal and offers an access request. */\n const { notify } = await import('@operato/layout')\n notify(notification)\n }\n) {\n return async (proposal: any, context: ProposalContext): Promise<ProposalOutcome> => {\n if (!context.sessionId || !proposal?.key) return { verdict: 'unavailable', errorCode: 'no-conversation' }\n /* Reviewed in the 작업 패널: entered only as it was shown there (design/ai/workspace.md §6-1) - here, and again at the server. */\n const reviewed = reviewedOf(proposal.key)\n if (!reviewStillHolds(proposal, reviewed)) return { verdict: 'refused', errorCode: 'review-stale', note: t('ai-assistant.entry.review-stale', { defaultValue: 'this proposal changed after it was reviewed in the workspace - review it again before entering.' }) } as any\n const answer = { ...(await apply(context.sessionId, proposal.key, reviewed)), op: proposal.op }\n if (answer.verdict === 'refused' && answer.errorCode === 'door-closed' && answer.refusal) {\n try {\n await reportRefusal({ level: 'error', message: answer.refusal.message, ex: [answer.refusal] })\n } catch {\n /* Notification delivery is best-effort; it never changes the stored proposal's refusal. */\n }\n }\n if (answer.errorCode === 'review-stale')\n return { verdict: 'refused', errorCode: 'review-stale', applied: 0, missed: 0, note: t('ai-assistant.entry.review-stale', { defaultValue: 'this proposal changed after it was reviewed in the workspace - review it again before entering.' }) }\n return {\n verdict: answer.verdict,\n ...(answer.errorCode ? { errorCode: answer.errorCode } : {}),\n applied: answer.applied ?? 0,\n missed: answer.missed ?? 0,\n note: entryOutcomeNote(answer, t)\n }\n }\n}\n\n/**\n * The newest card per record, over the conversation's proposals in order - an older one is replaced and\n * cannot be entered (the server refuses it as `superseded`; the card says so before anyone presses).\n */\nexport function latestEntryKeys(proposalsInOrder: any[][]): Map<string, string> {\n const latest = new Map<string, string>()\n for (const list of proposalsInOrder) for (const one of list ?? []) if (isEntryProposal(one) && one.key) latest.set(entryGroupOf(one), one.key)\n return latest\n}\n\n/** A 입력안 and a 수정안 of one record are counted apart - a newer one of the same kind replaces the older. */\nexport function entryGroupOf(proposal: any): string {\n return `${proposal?.target}:${proposal?.op ?? 'create'}`\n}\n\n/**\n * The card's title - the record's name from the target (`부서`, `거래처`) and the kind of card from here, so a\n * 수정안 is never titled as a 입력안.\n */\nexport function entryTitle(proposal: any, t: (key: string, options?: any) => string): string {\n const name = proposal?.labelKey ? t(proposal.labelKey) : proposal?.target ?? ''\n return proposal?.op === 'update'\n ? t('ai-assistant.entry.title-update', { defaultValue: '{name} to change', name })\n : t('ai-assistant.entry.title-create', { defaultValue: '{name} to enter', name })\n}\n\nexport function isChangeProposal(proposal: any): boolean {\n return isEntryProposal(proposal) && proposal.op === 'update'\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Reviewing a 입력안 in the AI 작업판 (operato-application/design/ai/workspace.md §6-1). Pure but for the
2
+ * Reviewing a 입력안 in the AI 작업 패널 (operato-application/design/ai/workspace.md §6-1). Pure but for the
3
3
  * session store - tested without a browser.
4
4
  *
5
5
  * The page is read-only: every column and every row, sorted as stored, issues marked. The one place that enters
@@ -1 +1 @@
1
- {"version":3,"file":"entry-workspace.js","sourceRoot":"","sources":["../../client/utils/entry-workspace.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Reviewing a 입력안 in the AI 작업판 (operato-application/design/ai/workspace.md §6-1). Pure but for the\n * session store - tested without a browser.\n *\n * The page is read-only: every column and every row, sorted as stored, issues marked. The one place that enters\n * is the dock's card. What the page showed is remembered by its fingerprint, and the card sends it - a proposal\n * that changed in between is refused, and the person reviews it again.\n */\nimport { entryProposalHash } from './entry-hash.js'\n\n/** A card wide or long enough not to be read in the dock. */\nexport const WORKSPACE_ROWS = 20\nexport const WORKSPACE_COLUMNS = 6\n\nexport function offersWorkspaceReview(proposal: any): boolean {\n return (proposal?.rows?.length ?? 0) > WORKSPACE_ROWS || (proposal?.columns?.length ?? 0) > WORKSPACE_COLUMNS\n}\n\nexport function entryReviewAddress(sessionId: string, proposalKey: string): string {\n return `assistant-workspace?${new URLSearchParams({ session: sessionId, entry: proposalKey }).toString()}`\n}\n\n/** The proposal of that key among the conversation's messages, and whether a newer card of the same record replaced it. */\nexport function entryForReview(messages: { id: string; createdAt?: string; proposalsJson?: any[] | null }[], key: string): { proposal?: any; createdAt?: string; superseded: boolean } {\n let at = -1\n let proposal: any\n let createdAt: string | undefined\n messages.forEach((message, i) => {\n for (const one of message.proposalsJson ?? []) if (one?.kind === 'entry' && one?.key === key) (at = i), (proposal = one), (createdAt = message.createdAt)\n })\n if (!proposal) return { superseded: false }\n const superseded = messages\n .slice(at + 1)\n .some(message => (message.proposalsJson ?? []).some(one => one?.kind === 'entry' && one?.target === proposal.target && (one?.op ?? 'create') === (proposal.op ?? 'create')))\n return { proposal, createdAt, superseded }\n}\n\nconst STORE = 'ai-assistant.entry-reviewed:'\n\n/** Remembers what the page showed for this key. A browser that keeps nothing keeps nothing - the card then sends no fingerprint. */\nexport function rememberReviewed(proposal: any, store: Pick<Storage, 'setItem'> | undefined = safeSession()): string {\n const hash = entryProposalHash(proposal)\n try {\n store?.setItem(STORE + proposal.key, hash)\n } catch {\n /* private window or blocked storage */\n }\n return hash\n}\n\nexport function reviewedHashOf(key: string, store: Pick<Storage, 'getItem'> | undefined = safeSession()): string | undefined {\n try {\n return store?.getItem(STORE + key) ?? undefined\n } catch {\n return undefined\n }\n}\n\n/** Whether the card may be pressed: not reviewed, or reviewed as it stands now. */\nexport function reviewStillHolds(proposal: any, reviewed: string | undefined): boolean {\n return !reviewed || reviewed === entryProposalHash(proposal)\n}\n\nfunction safeSession(): Storage | undefined {\n try {\n return typeof sessionStorage === 'undefined' ? undefined : sessionStorage\n } catch {\n return undefined\n }\n}\n"]}
1
+ {"version":3,"file":"entry-workspace.js","sourceRoot":"","sources":["../../client/utils/entry-workspace.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Reviewing a 입력안 in the AI 작업 패널 (operato-application/design/ai/workspace.md §6-1). Pure but for the\n * session store - tested without a browser.\n *\n * The page is read-only: every column and every row, sorted as stored, issues marked. The one place that enters\n * is the dock's card. What the page showed is remembered by its fingerprint, and the card sends it - a proposal\n * that changed in between is refused, and the person reviews it again.\n */\nimport { entryProposalHash } from './entry-hash.js'\n\n/** A card wide or long enough not to be read in the dock. */\nexport const WORKSPACE_ROWS = 20\nexport const WORKSPACE_COLUMNS = 6\n\nexport function offersWorkspaceReview(proposal: any): boolean {\n return (proposal?.rows?.length ?? 0) > WORKSPACE_ROWS || (proposal?.columns?.length ?? 0) > WORKSPACE_COLUMNS\n}\n\nexport function entryReviewAddress(sessionId: string, proposalKey: string): string {\n return `assistant-workspace?${new URLSearchParams({ session: sessionId, entry: proposalKey }).toString()}`\n}\n\n/** The proposal of that key among the conversation's messages, and whether a newer card of the same record replaced it. */\nexport function entryForReview(messages: { id: string; createdAt?: string; proposalsJson?: any[] | null }[], key: string): { proposal?: any; createdAt?: string; superseded: boolean } {\n let at = -1\n let proposal: any\n let createdAt: string | undefined\n messages.forEach((message, i) => {\n for (const one of message.proposalsJson ?? []) if (one?.kind === 'entry' && one?.key === key) (at = i), (proposal = one), (createdAt = message.createdAt)\n })\n if (!proposal) return { superseded: false }\n const superseded = messages\n .slice(at + 1)\n .some(message => (message.proposalsJson ?? []).some(one => one?.kind === 'entry' && one?.target === proposal.target && (one?.op ?? 'create') === (proposal.op ?? 'create')))\n return { proposal, createdAt, superseded }\n}\n\nconst STORE = 'ai-assistant.entry-reviewed:'\n\n/** Remembers what the page showed for this key. A browser that keeps nothing keeps nothing - the card then sends no fingerprint. */\nexport function rememberReviewed(proposal: any, store: Pick<Storage, 'setItem'> | undefined = safeSession()): string {\n const hash = entryProposalHash(proposal)\n try {\n store?.setItem(STORE + proposal.key, hash)\n } catch {\n /* private window or blocked storage */\n }\n return hash\n}\n\nexport function reviewedHashOf(key: string, store: Pick<Storage, 'getItem'> | undefined = safeSession()): string | undefined {\n try {\n return store?.getItem(STORE + key) ?? undefined\n } catch {\n return undefined\n }\n}\n\n/** Whether the card may be pressed: not reviewed, or reviewed as it stands now. */\nexport function reviewStillHolds(proposal: any, reviewed: string | undefined): boolean {\n return !reviewed || reviewed === entryProposalHash(proposal)\n}\n\nfunction safeSession(): Storage | undefined {\n try {\n return typeof sessionStorage === 'undefined' ? undefined : sessionStorage\n } catch {\n return undefined\n }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ /** pdf.js with its worker - the worker parses the file off the page's thread. */
2
+ export declare function loadPdfjs(): Promise<any>;
3
+ /** The longest side of a drawn page, in pixels - enough for a model to read print, small enough to send. */
4
+ export declare const PAGE_IMAGE_SIDE = 1600;
5
+ /** One page drawn into a JPEG. */
6
+ export declare function drawPdfPage(pdf: any, number: number): Promise<Blob>;
@@ -0,0 +1,34 @@
1
+ /*
2
+ * pdf.js on the screen - loaded only when a PDF is attached, its worker started once (document-text.ts reads the
3
+ * pages; this opens the file and draws a scanned page into an image).
4
+ */
5
+ let loading;
6
+ /** pdf.js with its worker - the worker parses the file off the page's thread. */
7
+ export function loadPdfjs() {
8
+ loading ??= (async () => {
9
+ const pdfjs = await import('pdfjs-dist');
10
+ if (!pdfjs.GlobalWorkerOptions.workerPort) {
11
+ pdfjs.GlobalWorkerOptions.workerPort = new Worker(new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url), { type: 'module' });
12
+ }
13
+ return pdfjs;
14
+ })();
15
+ return loading;
16
+ }
17
+ /** The longest side of a drawn page, in pixels - enough for a model to read print, small enough to send. */
18
+ export const PAGE_IMAGE_SIDE = 1600;
19
+ /** One page drawn into a JPEG. */
20
+ export async function drawPdfPage(pdf, number) {
21
+ const page = await pdf.getPage(number);
22
+ const base = page.getViewport({ scale: 1 });
23
+ const viewport = page.getViewport({ scale: Math.min(2, PAGE_IMAGE_SIDE / Math.max(base.width, base.height)) });
24
+ const canvas = document.createElement('canvas');
25
+ canvas.width = Math.ceil(viewport.width);
26
+ canvas.height = Math.ceil(viewport.height);
27
+ const context = canvas.getContext('2d');
28
+ /* A page draws on transparent pixels; a JPEG would turn them black. */
29
+ context.fillStyle = '#ffffff';
30
+ context.fillRect(0, 0, canvas.width, canvas.height);
31
+ await page.render({ canvas, canvasContext: context, viewport }).promise;
32
+ return await new Promise((resolve, reject) => canvas.toBlob(blob => (blob ? resolve(blob) : reject(new Error('the page could not be drawn'))), 'image/jpeg', 0.85));
33
+ }
34
+ //# sourceMappingURL=pdf-pages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-pages.js","sourceRoot":"","sources":["../../client/utils/pdf-pages.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,IAAI,OAAiC,CAAA;AAErC,iFAAiF;AACjF,MAAM,UAAU,SAAS;IACvB,OAAO,KAAK,CAAC,KAAK,IAAI,EAAE;QACtB,MAAM,KAAK,GAAQ,MAAM,MAAM,CAAC,YAAY,CAAC,CAAA;QAC7C,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,UAAU,EAAE,CAAC;YAC1C,KAAK,CAAC,mBAAmB,CAAC,UAAU,GAAG,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,qCAAqC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;QACxI,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC,CAAC,EAAE,CAAA;IACJ,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,4GAA4G;AAC5G,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAA;AAEnC,kCAAkC;AAClC,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAQ,EAAE,MAAc;IACxD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAA;IAC9G,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAA;IAC/C,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IACxC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC1C,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAE,CAAA;IACxC,uEAAuE;IACvE,OAAO,CAAC,SAAS,GAAG,SAAS,CAAA;IAC7B,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IACnD,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAA;IACvE,OAAO,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CACjD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,CACrH,CAAA;AACH,CAAC","sourcesContent":["/*\n * pdf.js on the screen - loaded only when a PDF is attached, its worker started once (document-text.ts reads the\n * pages; this opens the file and draws a scanned page into an image).\n */\nlet loading: Promise<any> | undefined\n\n/** pdf.js with its worker - the worker parses the file off the page's thread. */\nexport function loadPdfjs(): Promise<any> {\n loading ??= (async () => {\n const pdfjs: any = await import('pdfjs-dist')\n if (!pdfjs.GlobalWorkerOptions.workerPort) {\n pdfjs.GlobalWorkerOptions.workerPort = new Worker(new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url), { type: 'module' })\n }\n return pdfjs\n })()\n return loading\n}\n\n/** The longest side of a drawn page, in pixels - enough for a model to read print, small enough to send. */\nexport const PAGE_IMAGE_SIDE = 1600\n\n/** One page drawn into a JPEG. */\nexport async function drawPdfPage(pdf: any, number: number): Promise<Blob> {\n const page = await pdf.getPage(number)\n const base = page.getViewport({ scale: 1 })\n const viewport = page.getViewport({ scale: Math.min(2, PAGE_IMAGE_SIDE / Math.max(base.width, base.height)) })\n const canvas = document.createElement('canvas')\n canvas.width = Math.ceil(viewport.width)\n canvas.height = Math.ceil(viewport.height)\n const context = canvas.getContext('2d')!\n /* A page draws on transparent pixels; a JPEG would turn them black. */\n context.fillStyle = '#ffffff'\n context.fillRect(0, 0, canvas.width, canvas.height)\n await page.render({ canvas, canvasContext: context, viewport }).promise\n return await new Promise<Blob>((resolve, reject) =>\n canvas.toBlob(blob => (blob ? resolve(blob) : reject(new Error('the page could not be drawn'))), 'image/jpeg', 0.85)\n )\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  /*
2
- * The AI 작업판 on the screen - which versions a workspace has, and the table's sort and CSV
2
+ * The AI 작업 패널 on the screen - which versions a workspace has, and the table's sort and CSV
3
3
  * (operato-application/design/ai/workspace.md). Pure - tested without a browser.
4
4
  */
5
5
  export function isWorkspaceProposal(proposal) {
@@ -1 +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"]}
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../client/utils/workspace.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAsBH,MAAM,UAAU,mBAAmB,CAAC,QAAa;IAC/C,OAAO,QAAQ,EAAE,IAAI,KAAK,WAAW,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAA;AACtE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAA4E,EAAE,IAAY;IAC1H,MAAM,QAAQ,GAAuB,EAAE,CAAA;IACvC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC;YACnD,IAAI,mBAAmB,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBACtE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAA;YACrI,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,aAAa,CAAC,gBAAyB,EAAE,IAAY,EAAE,GAAW;IAChF,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,IAAI,EAAE,GAAG,CAAC,CAAA;IACV,KAAK,MAAM,IAAI,IAAI,gBAAgB;QACjC,KAAK,MAAM,GAAG,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;YAC7B,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI;gBAAE,SAAQ;YACtE,EAAE,IAAI,CAAC,CAAA;YACP,IAAI,GAAG,CAAC,GAAG,KAAK,GAAG;gBAAE,OAAO,GAAG,EAAE,CAAA;QACnC,CAAC;IACH,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;AACxB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,SAAiB,EAAE,IAAY,EAAE,OAAgB;IAChF,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC/D,IAAI,OAAO;QAAE,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;IAClD,OAAO,uBAAuB,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAA;AAClD,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,QAAQ,CAAC,IAA2B,EAAE,MAAc,EAAE,SAAyB,EAAE,MAAe;IAC9G,MAAM,MAAM,GAAG,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3C,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;IAC7D,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC7B,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAA;QACnB,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAA;QACnB,MAAM,MAAM,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE,CAAA;QACxD,MAAM,MAAM,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE,CAAA;QACxD,IAAI,MAAM,IAAI,MAAM;YAAE,OAAO,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QACpE,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAA;QAC3E,OAAO,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAA;IACxD,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,OAAO,GAAG,CAAC,KAAc,EAAE,EAAE;IACjC,MAAM,IAAI,GAAG,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACvE,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;AACvE,CAAC,CAAA;AAED,oGAAoG;AACpG,MAAM,UAAU,QAAQ,CAAC,OAA2C,EAAE,IAA2B;IAC/F,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IACrK,OAAO,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;AACjC,CAAC","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"]}
@@ -75,7 +75,7 @@ tslib_1.__decorate([
75
75
  tslib_1.__decorate([
76
76
  (0, type_graphql_1.Field)(() => graphql_scalars_1.GraphQLJSON, {
77
77
  nullable: true,
78
- description: "What this conversation accepts as attachments: { kinds: ('image'|'text'|'spreadsheet')[], maxBytes, maxFiles, maxChars }. Null: none."
78
+ description: "What this conversation accepts as attachments: { kinds: ('image'|'text'|'spreadsheet'|'document')[], maxBytes, maxFiles, maxChars }. Null: none."
79
79
  }),
80
80
  tslib_1.__metadata("design:type", Object)
81
81
  ], AssistantModeView.prototype, "attachments", void 0);
@@ -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 { 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"]}
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 { 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'|'document')[], 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"]}
@@ -27,7 +27,7 @@ export declare function turnSystemPrompt(modePrompt: string, screen?: {
27
27
  subjectLine?: string;
28
28
  }): string;
29
29
  /**
30
- * The categories a mode opens: its own, the AI 작업판 (design/ai/workspace.md), the attachment reader when it
30
+ * The categories a mode opens: its own, the AI 작업 패널 (design/ai/workspace.md), the attachment reader when it
31
31
  * takes files (design/ai/shared-dock.md §9-4), and one propose tool per record it may enter (design/ai/data-entry.md).
32
32
  */
33
33
  export declare function modeToolCategories(mode: {
@@ -41,7 +41,7 @@ export declare function modeToolCategories(mode: {
41
41
  /**
42
42
  * The categories an unattended run of a mode opens: its own and the lists it reads (design/ai/unattended-run.md §3).
43
43
  *
44
- * Not the 입력안 tools - nobody is there to press the card - nor the AI 작업판 or the attachment reader: there is no
44
+ * Not the 입력안 tools - nobody is there to press the card - nor the AI 작업 패널 or the attachment reader: there is no
45
45
  * conversation for a card or a file to belong to.
46
46
  */
47
47
  export declare function unattendedToolCategories(mode: {
@@ -85,7 +85,7 @@ type FrontMode = {
85
85
  */
86
86
  export declare function screenTargetKeys(mode: FrontMode, screen: TurnScreen, routeOf?: (key: string) => string | undefined): string[];
87
87
  /**
88
- * The categories a turn puts in front when the mode's tools do not fit in one call: the mode's own, the AI 작업판, the
88
+ * The categories a turn puts in front when the mode's tools do not fit in one call: the mode's own, the AI 작업 패널, the
89
89
  * people list and the invite card, the attachment reader, and the open screen's records. The rest the model opens with `openTools`.
90
90
  */
91
91
  export declare function modeFrontCategories(mode: FrontMode, screen?: TurnScreen): string[];
@@ -31,12 +31,12 @@ function turnSystemPrompt(modePrompt, screen = {}) {
31
31
  return [modePrompt, screen.subjectLine, ...pageRules].filter(Boolean).join('\n\n');
32
32
  }
33
33
  /**
34
- * The categories a mode opens: its own, the AI 작업판 (design/ai/workspace.md), the attachment reader when it
34
+ * The categories a mode opens: its own, the AI 작업 패널 (design/ai/workspace.md), the attachment reader when it
35
35
  * takes files (design/ai/shared-dock.md §9-4), and one propose tool per record it may enter (design/ai/data-entry.md).
36
36
  */
37
37
  function modeToolCategories(mode) {
38
38
  const extra = [
39
- /* Every conversation may put a large result on the AI 작업판 (design/ai/workspace.md). */
39
+ /* Every conversation may put a large result on the AI 작업 패널 (design/ai/workspace.md). */
40
40
  'assistant-workspace',
41
41
  /* Every conversation may read who works here and offer to bring one of them in (people/, ADR-0073 decision 3). */
42
42
  ...people_keys_js_1.PEOPLE_CATEGORIES,
@@ -50,7 +50,7 @@ function modeToolCategories(mode) {
50
50
  /**
51
51
  * The categories an unattended run of a mode opens: its own and the lists it reads (design/ai/unattended-run.md §3).
52
52
  *
53
- * Not the 입력안 tools - nobody is there to press the card - nor the AI 작업판 or the attachment reader: there is no
53
+ * Not the 입력안 tools - nobody is there to press the card - nor the AI 작업 패널 or the attachment reader: there is no
54
54
  * conversation for a card or a file to belong to.
55
55
  */
56
56
  function unattendedToolCategories(mode) {
@@ -97,7 +97,7 @@ function axesOfKey(mode, key) {
97
97
  return [key.split('.').pop(), ...card, ...answer];
98
98
  }
99
99
  /**
100
- * The categories a turn puts in front when the mode's tools do not fit in one call: the mode's own, the AI 작업판, the
100
+ * The categories a turn puts in front when the mode's tools do not fit in one call: the mode's own, the AI 작업 패널, the
101
101
  * people list and the invite card, the attachment reader, and the open screen's records. The rest the model opens with `openTools`.
102
102
  */
103
103
  function modeFrontCategories(mode, screen = {}) {
@@ -1 +1 @@
1
- {"version":3,"file":"mode-categories.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/mode-categories.ts"],"names":[],"mappings":"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","sourcesContent":["/* Read by the resolver and by the boot report, so the report and the conversation open the same tools. */\nimport { entryTargetOf } from '../data-entry/entry-target.js'\nimport { detailReadOf, readTargetOf } from '../data-read/read-target.js'\nimport { PEOPLE_CATEGORIES } from '../people/people-keys.js'\n/**\n * A category another package opens in every conversation - a dashboard card (ADR-0160 decision 9). It is in reach of\n * every conversation; it is put in front only on the pages it names, and found with `openTools` elsewhere (ADR-0061\n * decision 9). Never in an unattended run: its tools are proposals, and nobody is there to press the card.\n */\nexport interface ConversationCategory {\n name: string\n /** The application routes whose conversations put it in front (`my-dashboard`). */\n frontPages?: string[]\n /**\n * The axes of the records whose selection or anchor puts it in front (`dashboard-card`) - a card picked to change is\n * changed by this category's tools, wherever the dock is open.\n */\n frontAxes?: string[]\n /**\n * What a turn asked from one of its front pages must end in - said in that turn's system prompt only. On 나의 대시보드\n * a request to see a figure is a card; asked 「이번 달 지출 합계」 there, the model answered 「0원」 in words (2026-10-25).\n */\n pageRule?: string\n}\n\nconst conversationCategories = new Map<string, ConversationCategory>()\n\nexport function registerConversationCategory(category: ConversationCategory): void {\n conversationCategories.set(category.name, category)\n}\n\nexport function conversationCategoriesOf(): ConversationCategory[] {\n return [...conversationCategories.values()]\n}\n\nexport function clearConversationCategories(): void {\n conversationCategories.clear()\n}\n\n/** The system prompt of one turn - the mode's, the open record's line, and the rules of the page it is asked from. */\nexport function turnSystemPrompt(modePrompt: string, screen: { page?: string; subjectLine?: string } = {}): string {\n const pageRules = screen.page ? conversationCategoriesOf().filter(one => one.pageRule && (one.frontPages ?? []).includes(screen.page!)).map(one => one.pageRule!) : []\n return [modePrompt, screen.subjectLine, ...pageRules].filter(Boolean).join('\\n\\n')\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 /* Every conversation may read who works here and offer to bring one of them in (people/, ADR-0073 decision 3). */\n ...PEOPLE_CATEGORIES,\n ...(mode.attachments?.kinds?.length ? ['chat-attachments'] : []),\n ...(mode.entryTargets ?? []).map(key => `data-entry:${key}`),\n ...(mode.readTargets ?? []).map(key => `data-read:${key}`),\n ...conversationCategoriesOf().map(one => one.name)\n ]\n return [...new Set([...(mode.toolCategories ?? []), ...extra])]\n}\n\n/**\n * The categories an unattended run of a mode opens: its own and the lists it reads (design/ai/unattended-run.md §3).\n *\n * Not the 입력안 tools - nobody is there to press the card - nor the AI 작업판 or the attachment reader: there is no\n * conversation for a card or a file to belong to.\n */\nexport function unattendedToolCategories(mode: { toolCategories?: string[]; readTargets?: string[] }): string[] {\n return [...new Set([...(mode.toolCategories ?? []), ...(mode.readTargets ?? []).map(key => `data-read:${key}`)])]\n}\n\n/** The screen a turn is asked from - the page the dock is on, and the record its conversation is anchored to. */\nexport interface TurnScreen {\n /** The application's route of the open page (`cash-transaction-list`), as the dock sends it. */\n page?: string\n /** The axis of the record the conversation is anchored to (`contract` of `subject` · `contract:<id>`), from the stored anchor. */\n subjectAxis?: string\n /** The axes of the records the latest selection named (`contract` of `[keys] contracts contract:<id>`) - `selectionAxesOf`. */\n selectionAxes?: string[]\n}\n\n/** The line of a selection note the model reads - the client's `SELECTION_KEYS_PREFIX` (assistant-selection.ts). */\nexport const SELECTION_KEYS_LINE = '[keys]'\n\n/**\n * The axes of the records the latest selection note names (「AI와 작업」, shared-dock §2-11-1): a list hands its rows as\n * `<axis>:<uuid>`, so the rows' list is read with the tool of that axis. Put in front like an anchored record's - picked\n * on 미수금, the contracts' read was not in front, and the model looked for them among attachments and a dashboard's cards\n * before it opened the right tool (2026-10-25). A note is the server's (`appendSessionNote`, system); a person's words\n * are never read for it.\n */\nexport function selectionAxesOf(rows: { role?: string; content?: string | null }[]): string[] {\n for (let i = rows.length - 1; i >= 0; i--) {\n if (rows[i].role !== 'system') continue\n const line = String(rows[i].content ?? '')\n .split('\\n')\n .find(one => one.startsWith(SELECTION_KEYS_LINE))\n if (!line) continue\n const keys = line.slice(SELECTION_KEYS_LINE.length).trim().split(/\\s+/)\n return [...new Set(keys.map(one => one.match(/^([a-z0-9-]+):/)?.[1]).filter((one): one is string => !!one))]\n }\n return []\n}\n\ntype FrontMode = { toolCategories?: string[]; attachments?: { kinds?: string[] }; entryTargets?: string[]; readTargets?: string[] }\n\n/**\n * The records the open screen is about, among those the mode reads and enters (ADR-0061 decision 9). Nothing new is\n * declared: a read target's screen route is the page that lists it, and a key's last part is the axis of its record\n * (`office.contract` is the record of the axis `contract`) - reading and entering one record share its key.\n */\nexport function screenTargetKeys(mode: FrontMode, screen: TurnScreen, routeOf: (key: string) => string | undefined = key => readTargetOf(key)?.screen?.route): string[] {\n const keys = [...new Set([...(mode.readTargets ?? []), ...(mode.entryTargets ?? [])])]\n return keys.filter(\n key =>\n (!!screen.page && routeOf(key) === screen.page) ||\n (!!screen.subjectAxis && axesOfKey(mode, key).includes(screen.subjectAxis)) ||\n (!!screen.selectionAxes?.length && axesOfKey(mode, key).some(axis => screen.selectionAxes!.includes(axis)))\n )\n}\n\n/** The records a key is about: its last part, and the axes a card joins or a whole answer covers (EntryTarget.axes · DetailRead.axes). */\nfunction axesOfKey(mode: FrontMode, key: string): string[] {\n const card = (mode.entryTargets ?? []).includes(key) ? entryTargetOf(key)?.axes ?? [] : []\n const answer = (mode.readTargets ?? []).includes(key) ? detailReadOf(key)?.axes ?? [] : []\n return [key.split('.').pop()!, ...card, ...answer]\n}\n\n/**\n * The categories a turn puts in front when the mode's tools do not fit in one call: the mode's own, the AI 작업판, the\n * people list and the invite card, the attachment reader, and the open screen's records. The rest the model opens with `openTools`.\n */\nexport function modeFrontCategories(mode: FrontMode, screen: TurnScreen = {}): string[] {\n const keys = screenTargetKeys(mode, screen)\n return [\n ...new Set([\n ...(mode.toolCategories ?? []),\n 'assistant-workspace',\n /*\n * Two tools, always in front: 「정연희 초대해 줘」 asked in a conversation whose tools did not fit got the answer that\n * there was no tool to look people up (2026-10-24).\n */\n ...PEOPLE_CATEGORIES,\n ...(mode.attachments?.kinds?.length ? ['chat-attachments'] : []),\n ...keys.filter(key => (mode.readTargets ?? []).includes(key)).map(key => `data-read:${key}`),\n ...keys.filter(key => (mode.entryTargets ?? []).includes(key)).map(key => `data-entry:${key}`),\n ...conversationCategoriesOf()\n .filter(one => (!!screen.page && (one.frontPages ?? []).includes(screen.page)) || (one.frontAxes ?? []).some(axis => axis === screen.subjectAxis || !!screen.selectionAxes?.includes(axis)))\n .map(one => one.name)\n ])\n ]\n}\n\n/** Each screen the mode could be asked from - a page that lists one of its records, a record's axis - for the boot report. */\nexport function modeScreens(mode: FrontMode, routeOf: (key: string) => string | undefined = key => readTargetOf(key)?.screen?.route): TurnScreen[] {\n const keys = [...new Set([...(mode.readTargets ?? []), ...(mode.entryTargets ?? [])])]\n const pages = [...new Set([...keys.map(routeOf), ...conversationCategoriesOf().flatMap(one => one.frontPages ?? [])].filter(Boolean) as string[])]\n const axes = [...new Set(keys.flatMap(key => axesOfKey(mode, key)))]\n return [{}, ...pages.map(page => ({ page })), ...axes.map(subjectAxis => ({ subjectAxis }))]\n}\n"]}
1
+ {"version":3,"file":"mode-categories.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/mode-categories.ts"],"names":[],"mappings":"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","sourcesContent":["/* Read by the resolver and by the boot report, so the report and the conversation open the same tools. */\nimport { entryTargetOf } from '../data-entry/entry-target.js'\nimport { detailReadOf, readTargetOf } from '../data-read/read-target.js'\nimport { PEOPLE_CATEGORIES } from '../people/people-keys.js'\n/**\n * A category another package opens in every conversation - a dashboard card (ADR-0160 decision 9). It is in reach of\n * every conversation; it is put in front only on the pages it names, and found with `openTools` elsewhere (ADR-0061\n * decision 9). Never in an unattended run: its tools are proposals, and nobody is there to press the card.\n */\nexport interface ConversationCategory {\n name: string\n /** The application routes whose conversations put it in front (`my-dashboard`). */\n frontPages?: string[]\n /**\n * The axes of the records whose selection or anchor puts it in front (`dashboard-card`) - a card picked to change is\n * changed by this category's tools, wherever the dock is open.\n */\n frontAxes?: string[]\n /**\n * What a turn asked from one of its front pages must end in - said in that turn's system prompt only. On 나의 대시보드\n * a request to see a figure is a card; asked 「이번 달 지출 합계」 there, the model answered 「0원」 in words (2026-10-25).\n */\n pageRule?: string\n}\n\nconst conversationCategories = new Map<string, ConversationCategory>()\n\nexport function registerConversationCategory(category: ConversationCategory): void {\n conversationCategories.set(category.name, category)\n}\n\nexport function conversationCategoriesOf(): ConversationCategory[] {\n return [...conversationCategories.values()]\n}\n\nexport function clearConversationCategories(): void {\n conversationCategories.clear()\n}\n\n/** The system prompt of one turn - the mode's, the open record's line, and the rules of the page it is asked from. */\nexport function turnSystemPrompt(modePrompt: string, screen: { page?: string; subjectLine?: string } = {}): string {\n const pageRules = screen.page ? conversationCategoriesOf().filter(one => one.pageRule && (one.frontPages ?? []).includes(screen.page!)).map(one => one.pageRule!) : []\n return [modePrompt, screen.subjectLine, ...pageRules].filter(Boolean).join('\\n\\n')\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 /* Every conversation may read who works here and offer to bring one of them in (people/, ADR-0073 decision 3). */\n ...PEOPLE_CATEGORIES,\n ...(mode.attachments?.kinds?.length ? ['chat-attachments'] : []),\n ...(mode.entryTargets ?? []).map(key => `data-entry:${key}`),\n ...(mode.readTargets ?? []).map(key => `data-read:${key}`),\n ...conversationCategoriesOf().map(one => one.name)\n ]\n return [...new Set([...(mode.toolCategories ?? []), ...extra])]\n}\n\n/**\n * The categories an unattended run of a mode opens: its own and the lists it reads (design/ai/unattended-run.md §3).\n *\n * Not the 입력안 tools - nobody is there to press the card - nor the AI 작업 패널 or the attachment reader: there is no\n * conversation for a card or a file to belong to.\n */\nexport function unattendedToolCategories(mode: { toolCategories?: string[]; readTargets?: string[] }): string[] {\n return [...new Set([...(mode.toolCategories ?? []), ...(mode.readTargets ?? []).map(key => `data-read:${key}`)])]\n}\n\n/** The screen a turn is asked from - the page the dock is on, and the record its conversation is anchored to. */\nexport interface TurnScreen {\n /** The application's route of the open page (`cash-transaction-list`), as the dock sends it. */\n page?: string\n /** The axis of the record the conversation is anchored to (`contract` of `subject` · `contract:<id>`), from the stored anchor. */\n subjectAxis?: string\n /** The axes of the records the latest selection named (`contract` of `[keys] contracts contract:<id>`) - `selectionAxesOf`. */\n selectionAxes?: string[]\n}\n\n/** The line of a selection note the model reads - the client's `SELECTION_KEYS_PREFIX` (assistant-selection.ts). */\nexport const SELECTION_KEYS_LINE = '[keys]'\n\n/**\n * The axes of the records the latest selection note names (「AI와 작업」, shared-dock §2-11-1): a list hands its rows as\n * `<axis>:<uuid>`, so the rows' list is read with the tool of that axis. Put in front like an anchored record's - picked\n * on 미수금, the contracts' read was not in front, and the model looked for them among attachments and a dashboard's cards\n * before it opened the right tool (2026-10-25). A note is the server's (`appendSessionNote`, system); a person's words\n * are never read for it.\n */\nexport function selectionAxesOf(rows: { role?: string; content?: string | null }[]): string[] {\n for (let i = rows.length - 1; i >= 0; i--) {\n if (rows[i].role !== 'system') continue\n const line = String(rows[i].content ?? '')\n .split('\\n')\n .find(one => one.startsWith(SELECTION_KEYS_LINE))\n if (!line) continue\n const keys = line.slice(SELECTION_KEYS_LINE.length).trim().split(/\\s+/)\n return [...new Set(keys.map(one => one.match(/^([a-z0-9-]+):/)?.[1]).filter((one): one is string => !!one))]\n }\n return []\n}\n\ntype FrontMode = { toolCategories?: string[]; attachments?: { kinds?: string[] }; entryTargets?: string[]; readTargets?: string[] }\n\n/**\n * The records the open screen is about, among those the mode reads and enters (ADR-0061 decision 9). Nothing new is\n * declared: a read target's screen route is the page that lists it, and a key's last part is the axis of its record\n * (`office.contract` is the record of the axis `contract`) - reading and entering one record share its key.\n */\nexport function screenTargetKeys(mode: FrontMode, screen: TurnScreen, routeOf: (key: string) => string | undefined = key => readTargetOf(key)?.screen?.route): string[] {\n const keys = [...new Set([...(mode.readTargets ?? []), ...(mode.entryTargets ?? [])])]\n return keys.filter(\n key =>\n (!!screen.page && routeOf(key) === screen.page) ||\n (!!screen.subjectAxis && axesOfKey(mode, key).includes(screen.subjectAxis)) ||\n (!!screen.selectionAxes?.length && axesOfKey(mode, key).some(axis => screen.selectionAxes!.includes(axis)))\n )\n}\n\n/** The records a key is about: its last part, and the axes a card joins or a whole answer covers (EntryTarget.axes · DetailRead.axes). */\nfunction axesOfKey(mode: FrontMode, key: string): string[] {\n const card = (mode.entryTargets ?? []).includes(key) ? entryTargetOf(key)?.axes ?? [] : []\n const answer = (mode.readTargets ?? []).includes(key) ? detailReadOf(key)?.axes ?? [] : []\n return [key.split('.').pop()!, ...card, ...answer]\n}\n\n/**\n * The categories a turn puts in front when the mode's tools do not fit in one call: the mode's own, the AI 작업 패널, the\n * people list and the invite card, the attachment reader, and the open screen's records. The rest the model opens with `openTools`.\n */\nexport function modeFrontCategories(mode: FrontMode, screen: TurnScreen = {}): string[] {\n const keys = screenTargetKeys(mode, screen)\n return [\n ...new Set([\n ...(mode.toolCategories ?? []),\n 'assistant-workspace',\n /*\n * Two tools, always in front: 「정연희 초대해 줘」 asked in a conversation whose tools did not fit got the answer that\n * there was no tool to look people up (2026-10-24).\n */\n ...PEOPLE_CATEGORIES,\n ...(mode.attachments?.kinds?.length ? ['chat-attachments'] : []),\n ...keys.filter(key => (mode.readTargets ?? []).includes(key)).map(key => `data-read:${key}`),\n ...keys.filter(key => (mode.entryTargets ?? []).includes(key)).map(key => `data-entry:${key}`),\n ...conversationCategoriesOf()\n .filter(one => (!!screen.page && (one.frontPages ?? []).includes(screen.page)) || (one.frontAxes ?? []).some(axis => axis === screen.subjectAxis || !!screen.selectionAxes?.includes(axis)))\n .map(one => one.name)\n ])\n ]\n}\n\n/** Each screen the mode could be asked from - a page that lists one of its records, a record's axis - for the boot report. */\nexport function modeScreens(mode: FrontMode, routeOf: (key: string) => string | undefined = key => readTargetOf(key)?.screen?.route): TurnScreen[] {\n const keys = [...new Set([...(mode.readTargets ?? []), ...(mode.entryTargets ?? [])])]\n const pages = [...new Set([...keys.map(routeOf), ...conversationCategoriesOf().flatMap(one => one.frontPages ?? [])].filter(Boolean) as string[])]\n const axes = [...new Set(keys.flatMap(key => axesOfKey(mode, key)))]\n return [{}, ...pages.map(page => ({ page })), ...axes.map(subjectAxis => ({ subjectAxis }))]\n}\n"]}
@@ -1,4 +1,4 @@
1
- export type AttachmentKind = 'image' | 'text' | 'spreadsheet';
1
+ export type AttachmentKind = 'image' | 'text' | 'spreadsheet' | 'document';
2
2
  export interface AttachmentPolicy {
3
3
  kinds: AttachmentKind[];
4
4
  /** Per file. */
@@ -31,9 +31,9 @@ function attachmentKindOf(file) {
31
31
  return null;
32
32
  }
33
33
  /** `null` when every file may go; otherwise the first reason, by name - said before sending, never cut silently. */
34
- /** A kind the policy takes - text also when it takes spreadsheets, which arrive as the text of their sheets. */
34
+ /** A kind the policy takes - text also when it takes spreadsheets or documents, which arrive as their text. */
35
35
  function takes(policy, kind) {
36
- return policy.kinds.includes(kind) || (kind === 'text' && policy.kinds.includes('spreadsheet'));
36
+ return policy.kinds.includes(kind) || (kind === 'text' && (policy.kinds.includes('spreadsheet') || policy.kinds.includes('document')));
37
37
  }
38
38
  /** The characters of a text file the model reads under this policy. */
39
39
  function textCharsOf(policy) {
@@ -1 +1 @@
1
- {"version":3,"file":"attachment-policy.js","sourceRoot":"","sources":["../../../server/service/chat-session/attachment-policy.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * What a conversation accepts as attachments - declared by its mode (design/ai/shared-dock.md §9-1).\n *\n * Pure, so the rule is tested alone and the screen can say \"no\" before anything is sent. A mode that\n * declares nothing accepts nothing: the chat once took .csv and .json, carried them, and no code ever\n * showed them to the model - the person got an answer about a file it had never seen (AI standard §7).\n */\n\n/*\n * 'spreadsheet' is a kind a mode opens, never a file the door receives: the screen reads each .xlsx sheet into CSV\n * text before sending (client/utils/spreadsheet-text.ts), so a mode that takes spreadsheets takes that text.\n */\nexport type AttachmentKind = 'image' | 'text' | 'spreadsheet'\n\nexport interface AttachmentPolicy {\n kinds: AttachmentKind[]\n /** Per file. */\n maxBytes?: number\n /** Per message. */\n maxFiles?: number\n /** Per text file, the characters the model reads - a sheet read into text longer than this is refused, not cut. */\n maxChars?: number\n}\n\nexport const DEFAULT_MAX_BYTES = 5 * 1024 * 1024\nexport const DEFAULT_MAX_FILES = 5\n/** How much of a text file reaches the model in the turn it was sent. */\nexport const TEXT_CHARS_FOR_MODEL = 20_000\n\nconst TEXT_TYPES = ['text/plain', 'text/csv', 'text/markdown', 'application/json']\nconst TEXT_EXTENSIONS = ['.txt', '.csv', '.md', '.json']\n\n/** Which kind a file is, from its type - and its name when the browser did not know the type. */\nexport function attachmentKindOf(file: { type?: string; name?: string }): AttachmentKind | null {\n const type = (file.type || '').toLowerCase()\n if (type.startsWith('image/')) return 'image'\n if (TEXT_TYPES.includes(type)) return 'text'\n const name = (file.name || '').toLowerCase()\n if (TEXT_EXTENSIONS.some(ext => name.endsWith(ext))) return 'text'\n return null\n}\n\nexport type AttachmentRefusal =\n | { code: 'attachments-not-accepted' }\n | { code: 'attachment-kind-not-accepted'; name: string; kinds: AttachmentKind[] }\n | { code: 'attachment-too-large'; name: string; maxBytes: number }\n | { code: 'too-many-attachments'; maxFiles: number }\n\n/** `null` when every file may go; otherwise the first reason, by name - said before sending, never cut silently. */\n/** A kind the policy takes - text also when it takes spreadsheets, which arrive as the text of their sheets. */\nfunction takes(policy: AttachmentPolicy, kind: AttachmentKind): boolean {\n return policy.kinds.includes(kind) || (kind === 'text' && policy.kinds.includes('spreadsheet'))\n}\n\n/** The characters of a text file the model reads under this policy. */\nexport function textCharsOf(policy: AttachmentPolicy | null | undefined): number {\n return policy?.maxChars ?? TEXT_CHARS_FOR_MODEL\n}\n\nexport function refuseAttachments(\n policy: AttachmentPolicy | null | undefined,\n files: { name?: string; type?: string; size?: number }[]\n): AttachmentRefusal | null {\n if (!files.length) return null\n if (!policy || !policy.kinds?.length) return { code: 'attachments-not-accepted' }\n\n /*\n * File by file, in the order sent: its kind, then its size, then whether it is one too many. The screen's picking\n * rule (@operato/utils sortPickedFiles) says them in this order, so the same batch hears the same \"no\" from both.\n */\n const maxFiles = policy.maxFiles ?? DEFAULT_MAX_FILES\n const maxBytes = policy.maxBytes ?? DEFAULT_MAX_BYTES\n let taken = 0\n for (const file of files) {\n const kind = attachmentKindOf(file)\n if (!kind || !takes(policy, kind)) {\n return { code: 'attachment-kind-not-accepted', name: file.name || '', kinds: policy.kinds }\n }\n if ((file.size ?? 0) > maxBytes) return { code: 'attachment-too-large', name: file.name || '', maxBytes }\n if (taken >= maxFiles) return { code: 'too-many-attachments', maxFiles }\n taken++\n }\n return null\n}\n\n/** The line history keeps for a file once its turn is past - the model reads the file again only by asking. */\nexport function attachmentHistoryLine(file: { id: string; name?: string; mimetype?: string; size?: number | string }): string {\n return `[attachment ${file.id}: ${file.name || 'unnamed'} (${file.mimetype || 'unknown type'}, ${file.size ?? '?'} bytes) - readChatAttachment to open it]`\n}\n"]}
1
+ {"version":3,"file":"attachment-policy.js","sourceRoot":"","sources":["../../../server/service/chat-session/attachment-policy.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * What a conversation accepts as attachments - declared by its mode (design/ai/shared-dock.md §9-1).\n *\n * Pure, so the rule is tested alone and the screen can say \"no\" before anything is sent. A mode that\n * declares nothing accepts nothing: the chat once took .csv and .json, carried them, and no code ever\n * showed them to the model - the person got an answer about a file it had never seen (AI standard §7).\n */\n\n/*\n * 'spreadsheet' and 'document' are kinds a mode opens, never files the door receives: the screen reads each .xlsx sheet\n * into CSV text (client/utils/spreadsheet-text.ts), and each PDF, .docx and .pptx into text with its page and slide\n * numbers (client/utils/document-text.ts) before sending, so a mode that takes them takes that text. A scanned PDF\n * page arrives as an image, which the mode takes as 'image'.\n */\nexport type AttachmentKind = 'image' | 'text' | 'spreadsheet' | 'document'\n\nexport interface AttachmentPolicy {\n kinds: AttachmentKind[]\n /** Per file. */\n maxBytes?: number\n /** Per message. */\n maxFiles?: number\n /** Per text file, the characters the model reads - a sheet read into text longer than this is refused, not cut. */\n maxChars?: number\n}\n\nexport const DEFAULT_MAX_BYTES = 5 * 1024 * 1024\nexport const DEFAULT_MAX_FILES = 5\n/** How much of a text file reaches the model in the turn it was sent. */\nexport const TEXT_CHARS_FOR_MODEL = 20_000\n\nconst TEXT_TYPES = ['text/plain', 'text/csv', 'text/markdown', 'application/json']\nconst TEXT_EXTENSIONS = ['.txt', '.csv', '.md', '.json']\n\n/** Which kind a file is, from its type - and its name when the browser did not know the type. */\nexport function attachmentKindOf(file: { type?: string; name?: string }): AttachmentKind | null {\n const type = (file.type || '').toLowerCase()\n if (type.startsWith('image/')) return 'image'\n if (TEXT_TYPES.includes(type)) return 'text'\n const name = (file.name || '').toLowerCase()\n if (TEXT_EXTENSIONS.some(ext => name.endsWith(ext))) return 'text'\n return null\n}\n\nexport type AttachmentRefusal =\n | { code: 'attachments-not-accepted' }\n | { code: 'attachment-kind-not-accepted'; name: string; kinds: AttachmentKind[] }\n | { code: 'attachment-too-large'; name: string; maxBytes: number }\n | { code: 'too-many-attachments'; maxFiles: number }\n\n/** `null` when every file may go; otherwise the first reason, by name - said before sending, never cut silently. */\n/** A kind the policy takes - text also when it takes spreadsheets or documents, which arrive as their text. */\nfunction takes(policy: AttachmentPolicy, kind: AttachmentKind): boolean {\n return policy.kinds.includes(kind) || (kind === 'text' && (policy.kinds.includes('spreadsheet') || policy.kinds.includes('document')))\n}\n\n/** The characters of a text file the model reads under this policy. */\nexport function textCharsOf(policy: AttachmentPolicy | null | undefined): number {\n return policy?.maxChars ?? TEXT_CHARS_FOR_MODEL\n}\n\nexport function refuseAttachments(\n policy: AttachmentPolicy | null | undefined,\n files: { name?: string; type?: string; size?: number }[]\n): AttachmentRefusal | null {\n if (!files.length) return null\n if (!policy || !policy.kinds?.length) return { code: 'attachments-not-accepted' }\n\n /*\n * File by file, in the order sent: its kind, then its size, then whether it is one too many. The screen's picking\n * rule (@operato/utils sortPickedFiles) says them in this order, so the same batch hears the same \"no\" from both.\n */\n const maxFiles = policy.maxFiles ?? DEFAULT_MAX_FILES\n const maxBytes = policy.maxBytes ?? DEFAULT_MAX_BYTES\n let taken = 0\n for (const file of files) {\n const kind = attachmentKindOf(file)\n if (!kind || !takes(policy, kind)) {\n return { code: 'attachment-kind-not-accepted', name: file.name || '', kinds: policy.kinds }\n }\n if ((file.size ?? 0) > maxBytes) return { code: 'attachment-too-large', name: file.name || '', maxBytes }\n if (taken >= maxFiles) return { code: 'too-many-attachments', maxFiles }\n taken++\n }\n return null\n}\n\n/** The line history keeps for a file once its turn is past - the model reads the file again only by asking. */\nexport function attachmentHistoryLine(file: { id: string; name?: string; mimetype?: string; size?: number | string }): string {\n return `[attachment ${file.id}: ${file.name || 'unnamed'} (${file.mimetype || 'unknown type'}, ${file.size ?? '?'} bytes) - readChatAttachment to open it]`\n}\n"]}
@@ -44,7 +44,7 @@ export interface EntryApplyDeps {
44
44
  }>;
45
45
  state: any;
46
46
  report?(message: string, error: unknown): void;
47
- /** The fingerprint of what the AI 작업판 showed, when the person reviewed it there - entered only if the proposal is still that one. */
47
+ /** The fingerprint of what the AI 작업 패널 showed, when the person reviewed it there - entered only if the proposal is still that one. */
48
48
  reviewedHash?: string;
49
49
  }
50
50
  export declare function findEntryProposal(messages: StoredProposalMessage[], key: string): {
@@ -63,7 +63,7 @@ async function applyEntryProposal(key, deps, targetOf = entry_target_js_1.entryT
63
63
  return nothing('refused', 'superseded');
64
64
  if (deps.answered)
65
65
  return nothing('refused', 'already-answered');
66
- /* What the 작업판 showed and what is entered are one proposal (design/ai/workspace.md §6-1). */
66
+ /* What the 작업 패널 showed and what is entered are one proposal (design/ai/workspace.md §6-1). */
67
67
  if (deps.reviewedHash && deps.reviewedHash !== (0, entry_hash_js_1.entryProposalHash)(proposal))
68
68
  return nothing('refused', 'review-stale');
69
69
  const target = targetOf(proposal.target);