@things-factory/ai-assistant 10.1.234 → 10.1.241

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.
@@ -77,7 +77,7 @@ tslib_1.__decorate([
77
77
  tslib_1.__metadata("design:type", String)
78
78
  ], ChatMessage.prototype, "sessionId", void 0);
79
79
  tslib_1.__decorate([
80
- (0, typeorm_1.Column)({ type: 'varchar', length: 16 }),
80
+ (0, typeorm_1.Column)({ length: 16 }),
81
81
  (0, type_graphql_1.Field)({ description: "'user' | 'assistant' | 'system'" }),
82
82
  tslib_1.__metadata("design:type", String)
83
83
  ], ChatMessage.prototype, "role", void 0);
@@ -96,7 +96,7 @@ tslib_1.__decorate([
96
96
  tslib_1.__metadata("design:type", String)
97
97
  ], ChatMessage.prototype, "content", void 0);
98
98
  tslib_1.__decorate([
99
- (0, typeorm_1.Column)({ type: 'varchar', length: 16, nullable: true }),
99
+ (0, typeorm_1.Column)({ length: 16, nullable: true }),
100
100
  (0, type_graphql_1.Field)({ nullable: true, description: "Who this turn was for: 'assistant' | 'room'. Written by the door that stored it, never sent by the client." }),
101
101
  tslib_1.__metadata("design:type", String)
102
102
  ], ChatMessage.prototype, "addressee", void 0);
@@ -173,7 +173,7 @@ tslib_1.__decorate([
173
173
  tslib_1.__metadata("design:paramtypes", [])
174
174
  ], ChatMessage.prototype, "attachmentsJson", null);
175
175
  tslib_1.__decorate([
176
- (0, typeorm_1.Column)({ type: 'varchar', length: 1024, nullable: true }),
176
+ (0, typeorm_1.Column)({ type: 'text', nullable: true }),
177
177
  tslib_1.__metadata("design:type", String)
178
178
  ], ChatMessage.prototype, "groundingWarnings", void 0);
179
179
  tslib_1.__decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"chat-message.js","sourceRoot":"","sources":["../../../server/service/chat-message/chat-message.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ChatMessage — ChatSession 의 한 메시지.\n *\n * role: 'user' | 'assistant' | 'system'\n * 'system' 은 사용자 직접 편집/import 진행 등 시스템이 자동 생성하는 컨텍스트 메시지.\n *\n * relatedPatchId / relatedImportSessionId 로 메시지가 트리거한 패치·임포트와 연결.\n */\nimport {\n Column,\n CreateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, ID, ObjectType } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\n\nimport { User } from '@things-factory/auth-base'\nimport { config } from '@things-factory/env'\n\nimport { ChatSession } from '../chat-session/chat-session.js'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\n@Entity()\n@Index('ix_chat_message_1', (message: ChatMessage) => [message.session, message.createdAt])\n@ObjectType({ description: 'A single chat message in a ChatSession.' })\nexport class ChatMessage {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { nullable: true })\n readonly id?: string\n\n @ManyToOne(type => ChatSession, session => session.messages, { onDelete: 'CASCADE' })\n session?: ChatSession\n\n @RelationId((message: ChatMessage) => message.session)\n sessionId?: string\n\n @Column({ type: 'varchar', length: 16 })\n @Field({ description: \"'user' | 'assistant' | 'system'\" })\n role!: string\n\n @Column({\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined\n })\n @Field({ description: 'Message content' })\n content!: string\n\n /**\n * 이 말이 누구에게 한 말인가 — `'assistant'` · `'room'`(ADR-0070 A15 결정 ①).\n *\n * **값은 그 행을 쓴 문이 찍는다.** 어시스턴트에게 묻는 문(`assistantChat`)이 저장한 사람의 행은\n * `assistant` 이고, 사람끼리의 문(`appendSessionTurns`)이 저장한 행은 `room` 이다. 클라이언트가\n * 인자로 보내지 않는다 — 인자로 받으면 「어느 문을 불렀나」와 「무엇을 적어 보냈나」 두 사실이\n * 생기고, 둘은 어긋날 수 있다.\n *\n * 사람의 행에만 뜻이 있다. 어시스턴트의 답은 `parentMessage` 로 자기를 부른 턴에 귀속되고,\n * `system` 행은 누구에게 한 말이 아니다.\n */\n @Column({ type: 'varchar', length: 16, nullable: true })\n @Field({ nullable: true, description: \"Who this turn was for: 'assistant' | 'room'. Written by the door that stored it, never sent by the client.\" })\n addressee?: string\n\n /**\n * 이 행의 값이 지나온 문들 — ADR-0073 판정 1.\n *\n * 어시스턴트의 답과 제안 행에만 채워진다. 사람의 말은 빈 목록이다 — 사람이 자기 입으로 한 말에는\n * 문이 없다. 답을 저장할 때 그 턴에 부른 도구들의 `doors`(ADR-0072)를 합쳐 옮겨 적는다.\n *\n * 읽을 때마다 **읽는 사람**이 이 문을 전부 지나는지 보고, 못 지나면 본문 대신 한 줄이 나간다. 그래서\n * 이 칸은 \"누가 볼 수 있나\"를 적은 것이고, 저장된 값 자체는 누구에게도 달라지지 않는다.\n *\n * 목록이 비면 누구나 읽는다. 등록기가 모르는 도구가 답에 끼면 `unresolved:<도구>` 가 적히고 아무도\n * 못 지난다 — 모르는 값은 가려지는 쪽으로 넘어뜨린다.\n */\n @Column({ type: 'simple-array', nullable: true })\n @Field(() => [String], {\n nullable: true,\n description:\n 'GraphQL doors the tools behind this answer went through. Empty for what people typed. A reader who does not pass all of them gets a one-line notice in place of the content.'\n })\n doors?: string[]\n\n /**\n * 이 행이 **이 사람에게** 가려져 나갔는가 — 저장하지 않는다.\n *\n * 같은 행이 사람마다 다르게 나가므로 칸에 적을 값이 없다. 읽어 나갈 때 채운다. 화면은 이것으로\n * 자물쇠를 그리고, 본문에 온 한 줄을 어시스턴트의 말로 오해하지 않는다.\n */\n @Field({ nullable: true, description: 'True when the content was replaced because this reader does not pass the row doors.' })\n redacted?: boolean\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'PatchEntry.id this message triggered (if any).' })\n relatedPatchId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'ImportSession.id this message triggered (if any).' })\n relatedImportSessionId?: string\n\n /**\n * AI 가 응답을 만드는 동안 호출한 도구 trace — JSON-stringified.\n *\n * 컬럼은 string (PatchEntry.ops 와 동일 패턴), GraphQL 노출은 toolUsagesJson getter\n * 로 parse 된 객체. UI 의 fold-able 박스 (\"AI 가 이런 도구를 사용했습니다\") 가\n * 페이지 reload / loadHistory 후에도 살아있도록 영속.\n *\n * 형태: ToolUsage[] (시간순 trace). result 는 값이 아니라 모양(resultShapeOf — 크기 · 건수 · 키 이름 · 오류 코드,\n * ADR-0061 결정 8). 급여 · 지분 값이 대화 행 · 백업 · 내보내기로 복제되지 않는다.\n * null 이면 도구 호출 없었던 메시지 (text-only 응답).\n */\n @Column({\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined,\n nullable: true\n })\n toolUsages?: string\n\n /** GraphQL 노출용 — JSON 배열로 parse. */\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Tool usage trace — array of {name, arguments, result, kind}. Only on assistant messages.'\n })\n get toolUsagesJson(): any {\n if (!this.toolUsages) return null\n try {\n return JSON.parse(this.toolUsages)\n } catch {\n return null\n }\n }\n\n /**\n * The proposals this answer made - JSON-stringified, like `toolUsages` (ADR-0097 Q-D4 ④).\n *\n * A card lived only in the mutation's answer, so a refresh took it away, and with it the one place the\n * person could see that a proposal was requested and never answered. Stored, the card comes back with\n * its state read from the conversation's rows. Cleared with the body when the row is redacted\n * (`message-doors.ts` VALUE_CARRYING) - a proposal carries the same values as the answer.\n */\n @Column({\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined,\n nullable: true\n })\n proposals?: string\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'Proposals this answer made - the cards the panel draws. Only on assistant messages.' })\n get proposalsJson(): any {\n if (!this.proposals) return null\n try {\n return JSON.parse(this.proposals)\n } catch {\n return null\n }\n }\n\n /**\n * 접지 경고 — 이 답이 언급했으나 근거(프롬프트·보드 문맥·이력·도구 결과)에 없던 식별자 목록.\n *\n * **왜 영속하는가**: 도크의 협의는 여러 사람이 며칠에 걸쳐 읽는다. 경고가 그 자리에서만\n * 보이고 새로 고치면 사라지면, 나중에 읽는 사람은 검증되지 않은 문장을 검증된 것으로 읽는다.\n * 다시 계산할 수도 없다 — 근거였던 도구 결과 원문은 남지 않고 압축본만 남기 때문이다.\n *\n * 형태: string[] 를 JSON-stringify. 비었으면 null(= 접지 정상).\n */\n /**\n * The files sent with this turn - `[{ id, name, mimetype, size }]` as JSON (design/ai/shared-dock.md §9-2).\n * The files themselves live in attachment-base under the conversation and are opened only through it.\n */\n @Column({ type: 'text', nullable: true })\n attachments?: string\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'Files sent with this turn: [{ id, name, mimetype, size }].' })\n get attachmentsJson(): any {\n if (!this.attachments) return null\n try {\n return JSON.parse(this.attachments)\n } catch {\n return null\n }\n }\n\n @Column({ type: 'varchar', length: 1024, nullable: true })\n groundingWarnings?: string\n\n /** GraphQL 노출용 — 식별자 배열로 parse. */\n @Field(() => GraphQLJSON, {\n nullable: true,\n description: 'Ungrounded identifiers mentioned by this reply (hallucination candidates). Null when grounded.'\n })\n get groundingWarningsJson(): any {\n if (!this.groundingWarnings) return null\n try {\n return JSON.parse(this.groundingWarnings)\n } catch {\n return null\n }\n }\n\n /**\n * 메시지를 일으킨 사용자.\n * - role='user' → 보낸 사용자\n * - role='assistant' → 그 응답을 트리거한 사용자 (직전 user 메시지의 작성자)\n * - role='system' → 시스템 액션을 일으킨 사용자 (예: 직접 편집한 사용자)\n *\n * nullable — legacy 메시지 (이 필드 추가 이전에 작성) 는 null. 또한 ad-hoc 모드 / 시스템\n * 자동 메시지 중 사용자 컨텍스트가 없는 경우도 null 가능.\n */\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'User who triggered this message.' })\n creator?: User\n\n @RelationId((message: ChatMessage) => message.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Last user to update this message (for future edit support).' })\n updater?: User\n\n @RelationId((message: ChatMessage) => message.updater)\n updaterId?: string\n\n /**\n * 부모 메시지 — 이 메시지가 \"응답하는 대상\" / \"이어가는 메시지\" 의 id.\n *\n * 의미 (role 별):\n * - 'user' → 직전 in-session 메시지 (보통 assistant 또는 system). 첫 turn 은 null.\n * - 'assistant' → 이 응답이 답하는 user 메시지. (regenerate 로 같은 user 메시지를 부모로\n * 하는 형제 assistant 메시지가 생기면 자연스럽게 branching tree 가 됨)\n * - 'system' → 직전 in-session 메시지 (timeline 연속성 유지).\n *\n * 이 한 컬럼으로 향후 어떤 흐름이든 표현 가능 — linear chain / branching (regenerate) /\n * tree (replies-to-specific-message) 등. UI/쿼리는 점진 도입.\n *\n * onDelete: 'SET NULL' — 부모 메시지가 삭제돼도 child 는 남되 dangling 마킹.\n */\n @ManyToOne(type => ChatMessage, { nullable: true, onDelete: 'SET NULL' })\n parentMessage?: ChatMessage\n\n @RelationId((message: ChatMessage) => message.parentMessage)\n @Field({\n nullable: true,\n description:\n 'Id of the message this one is in-response-to / continues. Enables linear / branching / tree structures.'\n })\n parentMessageId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Creation timestamp.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'Last update timestamp (relevant once message editing is supported).' })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true, description: 'Soft delete timestamp.' })\n deletedAt?: Date\n}\n"]}
1
+ {"version":3,"file":"chat-message.js","sourceRoot":"","sources":["../../../server/service/chat-message/chat-message.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ChatMessage — ChatSession 의 한 메시지.\n *\n * role: 'user' | 'assistant' | 'system'\n * 'system' 은 사용자 직접 편집/import 진행 등 시스템이 자동 생성하는 컨텍스트 메시지.\n *\n * relatedPatchId / relatedImportSessionId 로 메시지가 트리거한 패치·임포트와 연결.\n */\nimport {\n Column,\n CreateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, ID, ObjectType } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\n\nimport { User } from '@things-factory/auth-base'\nimport { config } from '@things-factory/env'\n\nimport { ChatSession } from '../chat-session/chat-session.js'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\n@Entity()\n@Index('ix_chat_message_1', (message: ChatMessage) => [message.session, message.createdAt])\n@ObjectType({ description: 'A single chat message in a ChatSession.' })\nexport class ChatMessage {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { nullable: true })\n readonly id?: string\n\n @ManyToOne(type => ChatSession, session => session.messages, { onDelete: 'CASCADE' })\n session?: ChatSession\n\n @RelationId((message: ChatMessage) => message.session)\n sessionId?: string\n\n @Column({ length: 16 })\n @Field({ description: \"'user' | 'assistant' | 'system'\" })\n role!: string\n\n @Column({\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined\n })\n @Field({ description: 'Message content' })\n content!: string\n\n /**\n * 이 말이 누구에게 한 말인가 — `'assistant'` · `'room'`(ADR-0070 A15 결정 ①).\n *\n * **값은 그 행을 쓴 문이 찍는다.** 어시스턴트에게 묻는 문(`assistantChat`)이 저장한 사람의 행은\n * `assistant` 이고, 사람끼리의 문(`appendSessionTurns`)이 저장한 행은 `room` 이다. 클라이언트가\n * 인자로 보내지 않는다 — 인자로 받으면 「어느 문을 불렀나」와 「무엇을 적어 보냈나」 두 사실이\n * 생기고, 둘은 어긋날 수 있다.\n *\n * 사람의 행에만 뜻이 있다. 어시스턴트의 답은 `parentMessage` 로 자기를 부른 턴에 귀속되고,\n * `system` 행은 누구에게 한 말이 아니다.\n */\n @Column({ length: 16, nullable: true })\n @Field({ nullable: true, description: \"Who this turn was for: 'assistant' | 'room'. Written by the door that stored it, never sent by the client.\" })\n addressee?: string\n\n /**\n * 이 행의 값이 지나온 문들 — ADR-0073 판정 1.\n *\n * 어시스턴트의 답과 제안 행에만 채워진다. 사람의 말은 빈 목록이다 — 사람이 자기 입으로 한 말에는\n * 문이 없다. 답을 저장할 때 그 턴에 부른 도구들의 `doors`(ADR-0072)를 합쳐 옮겨 적는다.\n *\n * 읽을 때마다 **읽는 사람**이 이 문을 전부 지나는지 보고, 못 지나면 본문 대신 한 줄이 나간다. 그래서\n * 이 칸은 \"누가 볼 수 있나\"를 적은 것이고, 저장된 값 자체는 누구에게도 달라지지 않는다.\n *\n * 목록이 비면 누구나 읽는다. 등록기가 모르는 도구가 답에 끼면 `unresolved:<도구>` 가 적히고 아무도\n * 못 지난다 — 모르는 값은 가려지는 쪽으로 넘어뜨린다.\n */\n @Column({ type: 'simple-array', nullable: true })\n @Field(() => [String], {\n nullable: true,\n description:\n 'GraphQL doors the tools behind this answer went through. Empty for what people typed. A reader who does not pass all of them gets a one-line notice in place of the content.'\n })\n doors?: string[]\n\n /**\n * 이 행이 **이 사람에게** 가려져 나갔는가 — 저장하지 않는다.\n *\n * 같은 행이 사람마다 다르게 나가므로 칸에 적을 값이 없다. 읽어 나갈 때 채운다. 화면은 이것으로\n * 자물쇠를 그리고, 본문에 온 한 줄을 어시스턴트의 말로 오해하지 않는다.\n */\n @Field({ nullable: true, description: 'True when the content was replaced because this reader does not pass the row doors.' })\n redacted?: boolean\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'PatchEntry.id this message triggered (if any).' })\n relatedPatchId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'ImportSession.id this message triggered (if any).' })\n relatedImportSessionId?: string\n\n /**\n * AI 가 응답을 만드는 동안 호출한 도구 trace — JSON-stringified.\n *\n * 컬럼은 string (PatchEntry.ops 와 동일 패턴), GraphQL 노출은 toolUsagesJson getter\n * 로 parse 된 객체. UI 의 fold-able 박스 (\"AI 가 이런 도구를 사용했습니다\") 가\n * 페이지 reload / loadHistory 후에도 살아있도록 영속.\n *\n * 형태: ToolUsage[] (시간순 trace). result 는 값이 아니라 모양(resultShapeOf — 크기 · 건수 · 키 이름 · 오류 코드,\n * ADR-0061 결정 8). 급여 · 지분 값이 대화 행 · 백업 · 내보내기로 복제되지 않는다.\n * null 이면 도구 호출 없었던 메시지 (text-only 응답).\n */\n @Column({\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined,\n nullable: true\n })\n toolUsages?: string\n\n /** GraphQL 노출용 — JSON 배열로 parse. */\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Tool usage trace — array of {name, arguments, result, kind}. Only on assistant messages.'\n })\n get toolUsagesJson(): any {\n if (!this.toolUsages) return null\n try {\n return JSON.parse(this.toolUsages)\n } catch {\n return null\n }\n }\n\n /**\n * The proposals this answer made - JSON-stringified, like `toolUsages` (ADR-0097 Q-D4 ④).\n *\n * A card lived only in the mutation's answer, so a refresh took it away, and with it the one place the\n * person could see that a proposal was requested and never answered. Stored, the card comes back with\n * its state read from the conversation's rows. Cleared with the body when the row is redacted\n * (`message-doors.ts` VALUE_CARRYING) - a proposal carries the same values as the answer.\n */\n @Column({\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined,\n nullable: true\n })\n proposals?: string\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'Proposals this answer made - the cards the panel draws. Only on assistant messages.' })\n get proposalsJson(): any {\n if (!this.proposals) return null\n try {\n return JSON.parse(this.proposals)\n } catch {\n return null\n }\n }\n\n /**\n * 접지 경고 — 이 답이 언급했으나 근거(프롬프트·보드 문맥·이력·도구 결과)에 없던 식별자 목록.\n *\n * **왜 영속하는가**: 도크의 협의는 여러 사람이 며칠에 걸쳐 읽는다. 경고가 그 자리에서만\n * 보이고 새로 고치면 사라지면, 나중에 읽는 사람은 검증되지 않은 문장을 검증된 것으로 읽는다.\n * 다시 계산할 수도 없다 — 근거였던 도구 결과 원문은 남지 않고 압축본만 남기 때문이다.\n *\n * 형태: string[] 를 JSON-stringify. 비었으면 null(= 접지 정상).\n */\n /**\n * The files sent with this turn - `[{ id, name, mimetype, size }]` as JSON (design/ai/shared-dock.md §9-2).\n * The files themselves live in attachment-base under the conversation and are opened only through it.\n */\n @Column({ type: 'text', nullable: true })\n attachments?: string\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'Files sent with this turn: [{ id, name, mimetype, size }].' })\n get attachmentsJson(): any {\n if (!this.attachments) return null\n try {\n return JSON.parse(this.attachments)\n } catch {\n return null\n }\n }\n\n @Column({ type: 'text', nullable: true })\n groundingWarnings?: string\n\n /** GraphQL 노출용 — 식별자 배열로 parse. */\n @Field(() => GraphQLJSON, {\n nullable: true,\n description: 'Ungrounded identifiers mentioned by this reply (hallucination candidates). Null when grounded.'\n })\n get groundingWarningsJson(): any {\n if (!this.groundingWarnings) return null\n try {\n return JSON.parse(this.groundingWarnings)\n } catch {\n return null\n }\n }\n\n /**\n * 메시지를 일으킨 사용자.\n * - role='user' → 보낸 사용자\n * - role='assistant' → 그 응답을 트리거한 사용자 (직전 user 메시지의 작성자)\n * - role='system' → 시스템 액션을 일으킨 사용자 (예: 직접 편집한 사용자)\n *\n * nullable — legacy 메시지 (이 필드 추가 이전에 작성) 는 null. 또한 ad-hoc 모드 / 시스템\n * 자동 메시지 중 사용자 컨텍스트가 없는 경우도 null 가능.\n */\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'User who triggered this message.' })\n creator?: User\n\n @RelationId((message: ChatMessage) => message.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Last user to update this message (for future edit support).' })\n updater?: User\n\n @RelationId((message: ChatMessage) => message.updater)\n updaterId?: string\n\n /**\n * 부모 메시지 — 이 메시지가 \"응답하는 대상\" / \"이어가는 메시지\" 의 id.\n *\n * 의미 (role 별):\n * - 'user' → 직전 in-session 메시지 (보통 assistant 또는 system). 첫 turn 은 null.\n * - 'assistant' → 이 응답이 답하는 user 메시지. (regenerate 로 같은 user 메시지를 부모로\n * 하는 형제 assistant 메시지가 생기면 자연스럽게 branching tree 가 됨)\n * - 'system' → 직전 in-session 메시지 (timeline 연속성 유지).\n *\n * 이 한 컬럼으로 향후 어떤 흐름이든 표현 가능 — linear chain / branching (regenerate) /\n * tree (replies-to-specific-message) 등. UI/쿼리는 점진 도입.\n *\n * onDelete: 'SET NULL' — 부모 메시지가 삭제돼도 child 는 남되 dangling 마킹.\n */\n @ManyToOne(type => ChatMessage, { nullable: true, onDelete: 'SET NULL' })\n parentMessage?: ChatMessage\n\n @RelationId((message: ChatMessage) => message.parentMessage)\n @Field({\n nullable: true,\n description:\n 'Id of the message this one is in-response-to / continues. Enables linear / branching / tree structures.'\n })\n parentMessageId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Creation timestamp.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'Last update timestamp (relevant once message editing is supported).' })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true, description: 'Soft delete timestamp.' })\n deletedAt?: Date\n}\n"]}
@@ -104,12 +104,12 @@ tslib_1.__decorate([
104
104
  )
105
105
  ], ChatSession.prototype, "lastMessageAt", void 0);
106
106
  tslib_1.__decorate([
107
- (0, typeorm_1.Column)({ type: 'varchar', length: 64, nullable: true }),
107
+ (0, typeorm_1.Column)({ length: 64, nullable: true }),
108
108
  (0, type_graphql_1.Field)({ nullable: true, description: "Which conversation this session is — the registered mode kind. Written by the server, never sent by the client." }),
109
109
  tslib_1.__metadata("design:type", String)
110
110
  ], ChatSession.prototype, "kind", void 0);
111
111
  tslib_1.__decorate([
112
- (0, typeorm_1.Column)({ type: 'varchar', length: 16, default: 'open' }),
112
+ (0, typeorm_1.Column)({ length: 16, default: 'open' }),
113
113
  (0, type_graphql_1.Field)({ description: "Who may read this conversation: 'open' (anyone in the domain), 'group' (the owner and the people the owner brought in) or 'private' (the owner alone)." }),
114
114
  tslib_1.__metadata("design:type", String)
115
115
  ], ChatSession.prototype, "visibility", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"chat-session.js","sourceRoot":"","sources":["../../../server/service/chat-session/chat-session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ChatSession — Board 와 결합되는 AI 협력 세션.\n *\n * 한 보드에 여러 세션 가능 — DB 레벨 unique 제약 없음.\n *\n * 향후 폭증할 세션 분화 차원 (예: thread / 사용자별 / 컨텍스트별 design vs ops /\n * archived / forked from clone / agent별) 을 미리 차단하지 않기 위해 구조 자체는 1:N\n * 으로 열어둠. 단일 세션 정책 (현재 UX) 은 app 레벨 idempotent 로직 — `startBoardAISession`\n * 이 boardId 매칭 시 기존 세션 반환, `chatSessionByBoard` 가 첫 매칭 반환.\n *\n * 미래에 다중 세션 활성화 시 discriminator 컬럼 (kind / name / threadId / creatorId 등)\n * 을 그때 추가. 마이그레이션 부담 최소화 — 현재는 unique 만 풀어 자리 마련.\n *\n * board-service 의 Board 가 owning side 가 될 예정 (ChatSession FK).\n */\nimport {\n Column,\n CreateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n ManyToOne,\n OneToMany,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, ID, ObjectType } from 'type-graphql'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { config } from '@things-factory/env'\n\nimport { ChatMessage } from '../chat-message/chat-message.js'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\n@Entity()\n// 인덱스는 조회 성능용으로 유지 (chatSessionByBoard 가 [domain, boardId] 로 자주 쿼리).\n// unique 제약은 의도적으로 제거 — 한 보드에 여러 세션을 미래에 허용하기 위함.\n@Index('ix_chat_session_1', (session: ChatSession) => [session.domain, session.boardId])\n// 앵커 조회용 — chatSessionsByAnchor 가 [domain, anchorType, anchorId] 로 질의.\n@Index('ix_chat_session_2', (session: ChatSession) => [session.domain, session.anchorType, session.anchorId])\n@ObjectType({\n description: 'One AI conversation, anchored to what it is about. An anchor may have several — threads, one per person, one per context.'\n})\nexport class ChatSession {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { nullable: true })\n readonly id?: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true })\n domain?: Domain\n\n @RelationId((session: ChatSession) => session.domain)\n domainId?: string\n\n /** Board.id 와 연결. Board 가 owning side 가 될 예정. 1:N — 한 보드에 여러 세션 가능.\n *\n * **앵커 일반화 이후에도 유지되는 하위호환 컬럼**이다(제거하지 말 것). 앵커가 board 인 세션은\n * anchorId 와 함께 이 컬럼에도 같은 값을 쓴다(dual-write) — 기존 조회/인덱스/소비자가 그대로 동작한다. */\n @Column({ nullable: true })\n @Field({\n nullable: true,\n description:\n 'Connected Board id. Multiple sessions per board allowed (future: threads / per-user / contexts). Kept for backward compatibility — board-anchored sessions dual-write this alongside anchorId.'\n })\n boardId?: string\n\n /**\n * 대화가 매달린 대상의 **종류**. 세션은 보드에만 매달리지 않는다 —\n * 저작 대화는 보드에, 운영 대화는 공간(사이트)에 매달린다. 앵커가 대화의 성격과 이력 경계를 정한다.\n *\n * 'board' | 'space' | 'instance' | 'none'. 미설정(기존 데이터)은 boardId 가 있으면 board 로 읽는다 —\n * 백필 마이그레이션 없이 lazy 하위호환(컬럼 추가만).\n */\n @Column({ nullable: true })\n @Field({\n nullable: true,\n description:\n \"What this conversation is anchored to: 'board' | 'figure' (authoring a Figure) | 'application' | 'space' | 'instance' | 'domain' | 'subject' (one subject in a 360, '<axis>:<itemId>') | 'none'. Absent on legacy rows — treated as 'board' when boardId is present.\"\n })\n anchorType?: string\n\n /** 앵커 대상의 id(보드 id · 공간 id · 인스턴스 id). anchorType 과 함께 의미를 갖는다. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Id of the anchored target (board id / space id / instance id).' })\n anchorId?: string\n\n /**\n * 세션의 사용자 정의 이름 (탭 레이블 / 세션 식별 표시용).\n *\n * nullable — 미설정 시 UI 가 fallback 라벨 (\"세션 N\" 또는 createdAt 기반) 사용. 사용자가\n * 다중 세션 운영 시 의미 부여 (예: \"초기 설계\", \"운영 시뮬레이션\", \"사용자A 의 사적 세션\").\n */\n @Column({ nullable: true, length: 200 })\n @Field({ nullable: true, description: 'User-given name of this session (tab label / identification).' })\n name?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'User who created this session.' })\n creator?: User\n\n @RelationId((session: ChatSession) => session.creator)\n creatorId?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'User who last updated this session.' })\n updater?: User\n\n @RelationId((session: ChatSession) => session.updater)\n updaterId?: string\n\n /** 토큰 절감용 — 오래된 메시지를 LLM 으로 압축한 요약. Phase 2 에서 작성. */\n /**\n * 마지막 메시지 시각 — **비정규화**. 목록 정렬(최근 활동순)과 안 읽음 판정의 기준.\n *\n * 왜 비정규화인가: 세션마다 메시지를 집계하면(MAX/COUNT + group by) 목록 한 번에 집계 쿼리가 붙고,\n * 이 프레임워크는 5개 DB 드라이버를 지원해야 한다. 세션 컬럼 하나로 두면 정렬·비교가 값싸게 끝나고\n * 드라이버 차이가 없다. 메시지를 저장할 때 함께 갱신한다(activityPatch).\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Timestamp of the last message in this session (denormalized for list ordering and unread comparison — avoids per-session aggregation across the supported DB drivers).' })\n lastMessageAt?: Date\n\n /**\n * 어느 대화인가 — `twin.board` · `figure.catalog` 같은 모드의 kind (ADR-0072 결정 3).\n *\n * **서버가 적는다.** 클라이언트가 보내는 값이 아니다 — 등록된 모드에서 나온 것만 적힌다(`addressee`\n * 와 같은 결). 한 대상 위에 대화가 둘 설 수 있게 되면서(A17), 세션이 자기가 어느 대화인지 알아야\n * 한다. 몰랐을 때 화면에서 이렇게 나왔다: 모드를 바꿔도 같은 세션이 그대로 남아, 「도형 찾기」로\n * 바꾼 뒤 보낸 말이 앞의 고치기 대화에 붙었다(2026-09-18, Figure 레인 :3300).\n *\n * 값이 없는 세션은 이 칸이 서기 전에 난 것이다. 다음 턴에 요청한 kind 로 한 번 찍고, 찍힌 뒤에는\n * 「요청한 모드와 다르면 거절」 하나만 남는다. 마이그레이션은 만들지 않는다.\n */\n @Column({ type: 'varchar', length: 64, nullable: true })\n @Field({ nullable: true, description: \"Which conversation this session is — the registered mode kind. Written by the server, never sent by the client.\" })\n kind?: string\n\n /**\n * 이 대화를 도메인 안 누구나 읽는가 — ADR-0073 판정 2.\n *\n * `open` 도메인 안이면 누구나 들어와 읽고 말한다. 초대 절차는 없다.\n * `group` owner 가 들인 사람과 owner 만 읽고 말한다(2026-10-24, ADR-0073 결정 3).\n * `private` owner 혼자. 아무도 앉히지 않고, 그룹을 비공개로 바꾸면 들인 사람이 나간다.\n *\n * 새 세션은 **모두** `private` 으로 시작한다(2026-09-30 — `initialVisibility`). 앵커는 무엇에 대한\n * 이야기인지를 말할 뿐 누구에게 보이는지를 정하지 않는다. 칸의 기본값 `open` 은 칸이 서기 전의 옛 행을\n * 그날까지의 규칙대로 읽기 위한 것이고, 새 행은 늘 값을 적는다.\n *\n * owner 가 양방향으로 바꿀 수 있다. 공개로 바꾸기 전에 화면이 한 줄을 먼저 보이고, 공개 대화는 도크가\n * 다르게 그린다.\n *\n * open 이어도 답 행의 문 판정(ADR-0073 판정 1)은 그대로다 — 방에 들어온 것과 값을 읽는 것은 다르다.\n */\n @Column({ type: 'varchar', length: 16, default: 'open' })\n @Field({ description: \"Who may read this conversation: 'open' (anyone in the domain), 'group' (the owner and the people the owner brought in) or 'private' (the owner alone).\" })\n visibility!: string\n\n /** 목록에 보여줄 마지막 메시지 미리보기 — 목록에서 메시지 테이블을 다시 읽지 않기 위해 짧게 저장. */\n @Column({ nullable: true, length: 200 })\n @Field({ nullable: true, description: 'Short preview of the last message, stored so the session list does not need to query messages.' })\n lastMessagePreview?: string\n\n /**\n * 그 미리보기가 지나온 문 — ADR-0073 판정 1.\n *\n * 목록은 답을 **다시 읽는 두 번째 길**이다. 메시지 화면에서만 가리면 같은 문장이 목록에 그대로 뜬다.\n * 문이 둘이고 한쪽이 느슨하면 느슨한 쪽이 이긴다(ADR-0061 결정 1). 미리보기를 저장할 때 그 행의 문을\n * 함께 옮겨 적고, 목록도 같은 관문을 지난다.\n */\n @Column({ type: 'simple-array', nullable: true })\n @Field(() => [String], { nullable: true, description: 'Doors of the message the preview came from — the session list passes the same gate as the conversation.' })\n lastMessageDoors?: string[]\n\n @Column({\n nullable: true,\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined\n })\n @Field({ nullable: true, description: 'Compressed summary of older messages (for token saving).' })\n lastSummary?: string\n\n /**\n * `lastSummary` 가 **어디까지 덮는지** — 이 메시지까지 요약에 반영됐다.\n *\n * 이게 없으면 누적 요약을 할 수 없다: 매 턴 전체를 다시 요약하거나(비용) 이미 요약한 구간을\n * 또 이어 붙인다(중복). 이력에서 이 id 를 못 찾으면(삭제·편집) 덮은 범위를 알 수 없으므로\n * 전부 미반영으로 보고 다시 요약한다 — 빠뜨리는 쪽보다 안전하다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Id of the last message folded into lastSummary — enables incremental summarization instead of re-reading the whole history.' })\n summaryUpToMessageId?: string\n\n /** 사용 모델 식별 — 'anthropic:claude-sonnet-4-6' 등 */\n @Column({ nullable: true })\n @Field({ nullable: true })\n aiClientId?: string\n\n /** The agent that answered the last turn. Every turn's agent is in the AI usage rows. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The AI agent that answered the last turn.' })\n agentId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @DeleteDateColumn()\n deletedAt?: Date\n\n // ── 관계 (TypeORM 만, GraphQL 노출은 별도 query 통해 페이징) ──\n @OneToMany(type => ChatMessage, message => message.session)\n messages?: ChatMessage[]\n\n /*\n * ── 보드 패치 관계를 끊었다 (2026-09-10) ──────────────────────────────────\n * 여기 `@OneToMany(PatchEntry)` 가 있었다. 그래서 **대화 세션이 보드 편집기를 알아야** 했고,\n * 세션 계열을 중립 패키지로 옮기려는 순간 `ai-assistant → board-ai → ai-assistant` 순환이\n * 된다.\n *\n * 끊어도 잃는 것이 없다 — 이 관계를 **자료로 읽는 자리가 하나도 없었다**(전 저장소에서\n * `session.patches` 를 읽는 코드 0 건). 유일한 쓰임이 `PatchEntry` 쪽의 역방향 선언 한 줄이었고,\n * TypeORM 은 역방향 없이도 `@ManyToOne` 을 만든다. 패치는 `sessionId` 로 찾는다\n * (`chatPatches` 가 이미 그렇게 한다).\n *\n * 방향은 남는다: **보드 패치가 세션을 안다. 세션은 보드 패치를 모른다.** 종속이 한쪽이면\n * 중립 쪽이 옮겨질 수 있다.\n */\n}\n"]}
1
+ {"version":3,"file":"chat-session.js","sourceRoot":"","sources":["../../../server/service/chat-session/chat-session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ChatSession — Board 와 결합되는 AI 협력 세션.\n *\n * 한 보드에 여러 세션 가능 — DB 레벨 unique 제약 없음.\n *\n * 향후 폭증할 세션 분화 차원 (예: thread / 사용자별 / 컨텍스트별 design vs ops /\n * archived / forked from clone / agent별) 을 미리 차단하지 않기 위해 구조 자체는 1:N\n * 으로 열어둠. 단일 세션 정책 (현재 UX) 은 app 레벨 idempotent 로직 — `startBoardAISession`\n * 이 boardId 매칭 시 기존 세션 반환, `chatSessionByBoard` 가 첫 매칭 반환.\n *\n * 미래에 다중 세션 활성화 시 discriminator 컬럼 (kind / name / threadId / creatorId 등)\n * 을 그때 추가. 마이그레이션 부담 최소화 — 현재는 unique 만 풀어 자리 마련.\n *\n * board-service 의 Board 가 owning side 가 될 예정 (ChatSession FK).\n */\nimport {\n Column,\n CreateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n ManyToOne,\n OneToMany,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, ID, ObjectType } from 'type-graphql'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { config } from '@things-factory/env'\n\nimport { ChatMessage } from '../chat-message/chat-message.js'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\n@Entity()\n// 인덱스는 조회 성능용으로 유지 (chatSessionByBoard 가 [domain, boardId] 로 자주 쿼리).\n// unique 제약은 의도적으로 제거 — 한 보드에 여러 세션을 미래에 허용하기 위함.\n@Index('ix_chat_session_1', (session: ChatSession) => [session.domain, session.boardId])\n// 앵커 조회용 — chatSessionsByAnchor 가 [domain, anchorType, anchorId] 로 질의.\n@Index('ix_chat_session_2', (session: ChatSession) => [session.domain, session.anchorType, session.anchorId])\n@ObjectType({\n description: 'One AI conversation, anchored to what it is about. An anchor may have several — threads, one per person, one per context.'\n})\nexport class ChatSession {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { nullable: true })\n readonly id?: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true })\n domain?: Domain\n\n @RelationId((session: ChatSession) => session.domain)\n domainId?: string\n\n /** Board.id 와 연결. Board 가 owning side 가 될 예정. 1:N — 한 보드에 여러 세션 가능.\n *\n * **앵커 일반화 이후에도 유지되는 하위호환 컬럼**이다(제거하지 말 것). 앵커가 board 인 세션은\n * anchorId 와 함께 이 컬럼에도 같은 값을 쓴다(dual-write) — 기존 조회/인덱스/소비자가 그대로 동작한다. */\n @Column({ nullable: true })\n @Field({\n nullable: true,\n description:\n 'Connected Board id. Multiple sessions per board allowed (future: threads / per-user / contexts). Kept for backward compatibility — board-anchored sessions dual-write this alongside anchorId.'\n })\n boardId?: string\n\n /**\n * 대화가 매달린 대상의 **종류**. 세션은 보드에만 매달리지 않는다 —\n * 저작 대화는 보드에, 운영 대화는 공간(사이트)에 매달린다. 앵커가 대화의 성격과 이력 경계를 정한다.\n *\n * 'board' | 'space' | 'instance' | 'none'. 미설정(기존 데이터)은 boardId 가 있으면 board 로 읽는다 —\n * 백필 마이그레이션 없이 lazy 하위호환(컬럼 추가만).\n */\n @Column({ nullable: true })\n @Field({\n nullable: true,\n description:\n \"What this conversation is anchored to: 'board' | 'figure' (authoring a Figure) | 'application' | 'space' | 'instance' | 'domain' | 'subject' (one subject in a 360, '<axis>:<itemId>') | 'none'. Absent on legacy rows — treated as 'board' when boardId is present.\"\n })\n anchorType?: string\n\n /** 앵커 대상의 id(보드 id · 공간 id · 인스턴스 id). anchorType 과 함께 의미를 갖는다. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Id of the anchored target (board id / space id / instance id).' })\n anchorId?: string\n\n /**\n * 세션의 사용자 정의 이름 (탭 레이블 / 세션 식별 표시용).\n *\n * nullable — 미설정 시 UI 가 fallback 라벨 (\"세션 N\" 또는 createdAt 기반) 사용. 사용자가\n * 다중 세션 운영 시 의미 부여 (예: \"초기 설계\", \"운영 시뮬레이션\", \"사용자A 의 사적 세션\").\n */\n @Column({ nullable: true, length: 200 })\n @Field({ nullable: true, description: 'User-given name of this session (tab label / identification).' })\n name?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'User who created this session.' })\n creator?: User\n\n @RelationId((session: ChatSession) => session.creator)\n creatorId?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'User who last updated this session.' })\n updater?: User\n\n @RelationId((session: ChatSession) => session.updater)\n updaterId?: string\n\n /** 토큰 절감용 — 오래된 메시지를 LLM 으로 압축한 요약. Phase 2 에서 작성. */\n /**\n * 마지막 메시지 시각 — **비정규화**. 목록 정렬(최근 활동순)과 안 읽음 판정의 기준.\n *\n * 왜 비정규화인가: 세션마다 메시지를 집계하면(MAX/COUNT + group by) 목록 한 번에 집계 쿼리가 붙고,\n * 이 프레임워크는 5개 DB 드라이버를 지원해야 한다. 세션 컬럼 하나로 두면 정렬·비교가 값싸게 끝나고\n * 드라이버 차이가 없다. 메시지를 저장할 때 함께 갱신한다(activityPatch).\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Timestamp of the last message in this session (denormalized for list ordering and unread comparison — avoids per-session aggregation across the supported DB drivers).' })\n lastMessageAt?: Date\n\n /**\n * 어느 대화인가 — `twin.board` · `figure.catalog` 같은 모드의 kind (ADR-0072 결정 3).\n *\n * **서버가 적는다.** 클라이언트가 보내는 값이 아니다 — 등록된 모드에서 나온 것만 적힌다(`addressee`\n * 와 같은 결). 한 대상 위에 대화가 둘 설 수 있게 되면서(A17), 세션이 자기가 어느 대화인지 알아야\n * 한다. 몰랐을 때 화면에서 이렇게 나왔다: 모드를 바꿔도 같은 세션이 그대로 남아, 「도형 찾기」로\n * 바꾼 뒤 보낸 말이 앞의 고치기 대화에 붙었다(2026-09-18, Figure 레인 :3300).\n *\n * 값이 없는 세션은 이 칸이 서기 전에 난 것이다. 다음 턴에 요청한 kind 로 한 번 찍고, 찍힌 뒤에는\n * 「요청한 모드와 다르면 거절」 하나만 남는다. 마이그레이션은 만들지 않는다.\n */\n @Column({ length: 64, nullable: true })\n @Field({ nullable: true, description: \"Which conversation this session is — the registered mode kind. Written by the server, never sent by the client.\" })\n kind?: string\n\n /**\n * 이 대화를 도메인 안 누구나 읽는가 — ADR-0073 판정 2.\n *\n * `open` 도메인 안이면 누구나 들어와 읽고 말한다. 초대 절차는 없다.\n * `group` owner 가 들인 사람과 owner 만 읽고 말한다(2026-10-24, ADR-0073 결정 3).\n * `private` owner 혼자. 아무도 앉히지 않고, 그룹을 비공개로 바꾸면 들인 사람이 나간다.\n *\n * 새 세션은 **모두** `private` 으로 시작한다(2026-09-30 — `initialVisibility`). 앵커는 무엇에 대한\n * 이야기인지를 말할 뿐 누구에게 보이는지를 정하지 않는다. 칸의 기본값 `open` 은 칸이 서기 전의 옛 행을\n * 그날까지의 규칙대로 읽기 위한 것이고, 새 행은 늘 값을 적는다.\n *\n * owner 가 양방향으로 바꿀 수 있다. 공개로 바꾸기 전에 화면이 한 줄을 먼저 보이고, 공개 대화는 도크가\n * 다르게 그린다.\n *\n * open 이어도 답 행의 문 판정(ADR-0073 판정 1)은 그대로다 — 방에 들어온 것과 값을 읽는 것은 다르다.\n */\n @Column({ length: 16, default: 'open' })\n @Field({ description: \"Who may read this conversation: 'open' (anyone in the domain), 'group' (the owner and the people the owner brought in) or 'private' (the owner alone).\" })\n visibility!: string\n\n /** 목록에 보여줄 마지막 메시지 미리보기 — 목록에서 메시지 테이블을 다시 읽지 않기 위해 짧게 저장. */\n @Column({ nullable: true, length: 200 })\n @Field({ nullable: true, description: 'Short preview of the last message, stored so the session list does not need to query messages.' })\n lastMessagePreview?: string\n\n /**\n * 그 미리보기가 지나온 문 — ADR-0073 판정 1.\n *\n * 목록은 답을 **다시 읽는 두 번째 길**이다. 메시지 화면에서만 가리면 같은 문장이 목록에 그대로 뜬다.\n * 문이 둘이고 한쪽이 느슨하면 느슨한 쪽이 이긴다(ADR-0061 결정 1). 미리보기를 저장할 때 그 행의 문을\n * 함께 옮겨 적고, 목록도 같은 관문을 지난다.\n */\n @Column({ type: 'simple-array', nullable: true })\n @Field(() => [String], { nullable: true, description: 'Doors of the message the preview came from — the session list passes the same gate as the conversation.' })\n lastMessageDoors?: string[]\n\n @Column({\n nullable: true,\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined\n })\n @Field({ nullable: true, description: 'Compressed summary of older messages (for token saving).' })\n lastSummary?: string\n\n /**\n * `lastSummary` 가 **어디까지 덮는지** — 이 메시지까지 요약에 반영됐다.\n *\n * 이게 없으면 누적 요약을 할 수 없다: 매 턴 전체를 다시 요약하거나(비용) 이미 요약한 구간을\n * 또 이어 붙인다(중복). 이력에서 이 id 를 못 찾으면(삭제·편집) 덮은 범위를 알 수 없으므로\n * 전부 미반영으로 보고 다시 요약한다 — 빠뜨리는 쪽보다 안전하다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Id of the last message folded into lastSummary — enables incremental summarization instead of re-reading the whole history.' })\n summaryUpToMessageId?: string\n\n /** 사용 모델 식별 — 'anthropic:claude-sonnet-4-6' 등 */\n @Column({ nullable: true })\n @Field({ nullable: true })\n aiClientId?: string\n\n /** The agent that answered the last turn. Every turn's agent is in the AI usage rows. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The AI agent that answered the last turn.' })\n agentId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @DeleteDateColumn()\n deletedAt?: Date\n\n // ── 관계 (TypeORM 만, GraphQL 노출은 별도 query 통해 페이징) ──\n @OneToMany(type => ChatMessage, message => message.session)\n messages?: ChatMessage[]\n\n /*\n * ── 보드 패치 관계를 끊었다 (2026-09-10) ──────────────────────────────────\n * 여기 `@OneToMany(PatchEntry)` 가 있었다. 그래서 **대화 세션이 보드 편집기를 알아야** 했고,\n * 세션 계열을 중립 패키지로 옮기려는 순간 `ai-assistant → board-ai → ai-assistant` 순환이\n * 된다.\n *\n * 끊어도 잃는 것이 없다 — 이 관계를 **자료로 읽는 자리가 하나도 없었다**(전 저장소에서\n * `session.patches` 를 읽는 코드 0 건). 유일한 쓰임이 `PatchEntry` 쪽의 역방향 선언 한 줄이었고,\n * TypeORM 은 역방향 없이도 `@ManyToOne` 을 만든다. 패치는 `sessionId` 로 찾는다\n * (`chatPatches` 가 이미 그렇게 한다).\n *\n * 방향은 남는다: **보드 패치가 세션을 안다. 세션은 보드 패치를 모른다.** 종속이 한쪽이면\n * 중립 쪽이 옮겨질 수 있다.\n */\n}\n"]}
@@ -54,7 +54,7 @@ tslib_1.__decorate([
54
54
  tslib_1.__metadata("design:type", String)
55
55
  ], ChatSessionParticipant.prototype, "userId", void 0);
56
56
  tslib_1.__decorate([
57
- (0, typeorm_1.Column)({ type: 'varchar', length: 16, default: 'member' }),
57
+ (0, typeorm_1.Column)({ length: 16, default: 'member' }),
58
58
  (0, type_graphql_1.Field)({ description: 'Role of this participant: owner | member | viewer.' }),
59
59
  tslib_1.__metadata("design:type", String)
60
60
  ], ChatSessionParticipant.prototype, "role", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"chat-session-participant.js","sourceRoot":"","sources":["../../../server/service/chat-session-participant/chat-session-participant.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ChatSessionParticipant — 채팅 세션의 참여 사용자.\n *\n * 한 ChatSession 에 여러 사용자가 참여할 수 있고, 각자 role (owner / member / viewer 등)\n * 을 가짐. 다자간 채팅 / 권한 분화 / \"내 세션 목록\" / 멤버 표시 등의 기반.\n *\n * 현재 visibility 정책 (도메인 내 모두 공개) 은 그대로 유지 — 본 엔티티는 영속/추적\n * 데이터 만들고, \"참여자만 접근\" 같은 enforcement 는 정책 굳을 때 별도 phase 에서 도입.\n *\n * 자동 참여 vs 명시적 초대 정책도 미정 — 일단 세션 creator 만 자동 'owner' 로 등록.\n */\nimport {\n Column,\n CreateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, ID, ObjectType } from 'type-graphql'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\n\nimport { ChatSession } from '../chat-session/chat-session.js'\n\n@Entity('chat_session_participants')\n@Index('ix_chat_session_participant_0', ['domain', 'session', 'user'], {\n unique: true,\n where: '\"deleted_at\" IS NULL'\n})\n@Index('ix_chat_session_participant_1', ['domain', 'user'], {\n where: '\"deleted_at\" IS NULL'\n})\n@ObjectType({\n description:\n 'Participant of a ChatSession — links a User to a session with a role. Foundation for multi-user chat / per-user filtering / member display.'\n})\nexport class ChatSessionParticipant {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique participant id.' })\n readonly id?: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Domain to which this participant belongs.' })\n domain?: Domain\n\n @RelationId((p: ChatSessionParticipant) => p.domain)\n domainId?: string\n\n @ManyToOne(type => ChatSession, { onDelete: 'CASCADE' })\n @Field(type => ChatSession, { nullable: true, description: 'The ChatSession this participation belongs to.' })\n session?: ChatSession\n\n @RelationId((p: ChatSessionParticipant) => p.session)\n sessionId?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'The user participating in the session.' })\n user?: User\n\n @RelationId((p: ChatSessionParticipant) => p.user)\n userId?: string\n\n /**\n * 참여자의 역할.\n * - 'owner': 세션 만든 사람. rename / delete 권한.\n * - 'member': 일반 참여자. 메시지 송수신.\n * - 'viewer': 읽기 전용 (미래 권한 정책 활성화 시).\n *\n * 시작은 'owner' / 'member' 둘로 충분. 더 세분화 필요해지면 enum 확장.\n */\n @Column({ type: 'varchar', length: 16, default: 'member' })\n @Field({ description: 'Role of this participant: owner | member | viewer.' })\n role!: string\n\n /** 마지막 활동 시각 — presence indicator. nullable. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Last seen / activity timestamp (presence indicator).' })\n lastSeenAt?: Date\n\n /**\n * 이 참여자가 **읽은 지점**. 세션의 마지막 활동 시각과 비교해 안 읽음을 판정한다.\n *\n * `lastSeenAt`(접속 표시)과 다른 개념이다 — 창을 열어 두고 화면을 안 볼 수 있고, 접속이 끊긴 뒤에도\n * 읽은 지점은 남는다. 두 값을 섞어 쓰면 \"봤는데 안 읽음\" 또는 그 반대가 된다.\n * nullable — 한 번도 읽지 않은 참여자(초대 직후)는 세션의 모든 활동이 안 읽음이다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Point up to which this participant has read the session. Distinct from lastSeenAt (presence): a window can be open without being read, and the read point survives disconnection. Null means nothing read yet.' })\n lastReadAt?: Date\n\n /**\n * 이 사용자가 **자기 목록에서 숨긴 시각**. 숨김은 **나에게만** 적용된다 — 남의 목록·대화 내용은 그대로다.\n *\n * 왜 삭제와 나누는가: 협의는 여럿의 기록이므로 지우는 것은 **만든 사람**만 할 수 있다(되돌릴 수 없다).\n * 그 권한이 없는 사람에게도 \"내 목록을 정리할\" 방법은 있어야 하고, 그것이 숨김이다.\n *\n * 숨긴 **뒤에 새 활동이 생기면 다시 나타난다** — 아직 참여자인 대화에서 조용히 잘려 나가는 것이\n * 목록이 한 줄 늘어나는 것보다 나쁘다. 완전히 빠지려면 대화에서 나가야 한다(별개 기능).\n * nullable — 값이 없으면 숨기지 않은 상태.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When this user hid the session from their own list. Per-user only; the session and other participants are unaffected. Activity newer than this timestamp makes it reappear.' })\n hiddenAt?: Date\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'User who created this participant record (typically session owner who invited).' })\n creator?: User\n\n @RelationId((p: ChatSessionParticipant) => p.creator)\n creatorId?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'Last user to update this participant record (e.g., role change).' })\n updater?: User\n\n @RelationId((p: ChatSessionParticipant) => p.updater)\n updaterId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Joined-at timestamp.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'Last update timestamp (role change / lastSeenAt bump).' })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true, description: 'Soft delete (= left session) timestamp.' })\n deletedAt?: Date\n}\n"]}
1
+ {"version":3,"file":"chat-session-participant.js","sourceRoot":"","sources":["../../../server/service/chat-session-participant/chat-session-participant.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ChatSessionParticipant — 채팅 세션의 참여 사용자.\n *\n * 한 ChatSession 에 여러 사용자가 참여할 수 있고, 각자 role (owner / member / viewer 등)\n * 을 가짐. 다자간 채팅 / 권한 분화 / \"내 세션 목록\" / 멤버 표시 등의 기반.\n *\n * 현재 visibility 정책 (도메인 내 모두 공개) 은 그대로 유지 — 본 엔티티는 영속/추적\n * 데이터 만들고, \"참여자만 접근\" 같은 enforcement 는 정책 굳을 때 별도 phase 에서 도입.\n *\n * 자동 참여 vs 명시적 초대 정책도 미정 — 일단 세션 creator 만 자동 'owner' 로 등록.\n */\nimport {\n Column,\n CreateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, ID, ObjectType } from 'type-graphql'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\n\nimport { ChatSession } from '../chat-session/chat-session.js'\n\n@Entity('chat_session_participants')\n@Index('ix_chat_session_participant_0', ['domain', 'session', 'user'], {\n unique: true,\n where: '\"deleted_at\" IS NULL'\n})\n@Index('ix_chat_session_participant_1', ['domain', 'user'], {\n where: '\"deleted_at\" IS NULL'\n})\n@ObjectType({\n description:\n 'Participant of a ChatSession — links a User to a session with a role. Foundation for multi-user chat / per-user filtering / member display.'\n})\nexport class ChatSessionParticipant {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique participant id.' })\n readonly id?: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Domain to which this participant belongs.' })\n domain?: Domain\n\n @RelationId((p: ChatSessionParticipant) => p.domain)\n domainId?: string\n\n @ManyToOne(type => ChatSession, { onDelete: 'CASCADE' })\n @Field(type => ChatSession, { nullable: true, description: 'The ChatSession this participation belongs to.' })\n session?: ChatSession\n\n @RelationId((p: ChatSessionParticipant) => p.session)\n sessionId?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'The user participating in the session.' })\n user?: User\n\n @RelationId((p: ChatSessionParticipant) => p.user)\n userId?: string\n\n /**\n * 참여자의 역할.\n * - 'owner': 세션 만든 사람. rename / delete 권한.\n * - 'member': 일반 참여자. 메시지 송수신.\n * - 'viewer': 읽기 전용 (미래 권한 정책 활성화 시).\n *\n * 시작은 'owner' / 'member' 둘로 충분. 더 세분화 필요해지면 enum 확장.\n */\n @Column({ length: 16, default: 'member' })\n @Field({ description: 'Role of this participant: owner | member | viewer.' })\n role!: string\n\n /** 마지막 활동 시각 — presence indicator. nullable. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Last seen / activity timestamp (presence indicator).' })\n lastSeenAt?: Date\n\n /**\n * 이 참여자가 **읽은 지점**. 세션의 마지막 활동 시각과 비교해 안 읽음을 판정한다.\n *\n * `lastSeenAt`(접속 표시)과 다른 개념이다 — 창을 열어 두고 화면을 안 볼 수 있고, 접속이 끊긴 뒤에도\n * 읽은 지점은 남는다. 두 값을 섞어 쓰면 \"봤는데 안 읽음\" 또는 그 반대가 된다.\n * nullable — 한 번도 읽지 않은 참여자(초대 직후)는 세션의 모든 활동이 안 읽음이다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Point up to which this participant has read the session. Distinct from lastSeenAt (presence): a window can be open without being read, and the read point survives disconnection. Null means nothing read yet.' })\n lastReadAt?: Date\n\n /**\n * 이 사용자가 **자기 목록에서 숨긴 시각**. 숨김은 **나에게만** 적용된다 — 남의 목록·대화 내용은 그대로다.\n *\n * 왜 삭제와 나누는가: 협의는 여럿의 기록이므로 지우는 것은 **만든 사람**만 할 수 있다(되돌릴 수 없다).\n * 그 권한이 없는 사람에게도 \"내 목록을 정리할\" 방법은 있어야 하고, 그것이 숨김이다.\n *\n * 숨긴 **뒤에 새 활동이 생기면 다시 나타난다** — 아직 참여자인 대화에서 조용히 잘려 나가는 것이\n * 목록이 한 줄 늘어나는 것보다 나쁘다. 완전히 빠지려면 대화에서 나가야 한다(별개 기능).\n * nullable — 값이 없으면 숨기지 않은 상태.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When this user hid the session from their own list. Per-user only; the session and other participants are unaffected. Activity newer than this timestamp makes it reappear.' })\n hiddenAt?: Date\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'User who created this participant record (typically session owner who invited).' })\n creator?: User\n\n @RelationId((p: ChatSessionParticipant) => p.creator)\n creatorId?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'Last user to update this participant record (e.g., role change).' })\n updater?: User\n\n @RelationId((p: ChatSessionParticipant) => p.updater)\n updaterId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Joined-at timestamp.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'Last update timestamp (role change / lastSeenAt bump).' })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true, description: 'Soft delete (= left session) timestamp.' })\n deletedAt?: Date\n}\n"]}
@@ -176,7 +176,7 @@ tslib_1.__decorate([
176
176
  tslib_1.__metadata("design:paramtypes", [])
177
177
  ], AssistantRun.prototype, "attemptsJson", null);
178
178
  tslib_1.__decorate([
179
- (0, typeorm_1.Column)({ type: 'varchar', length: 1024, nullable: true }),
179
+ (0, typeorm_1.Column)({ type: 'text', nullable: true }),
180
180
  tslib_1.__metadata("design:type", String)
181
181
  ], AssistantRun.prototype, "groundingWarnings", void 0);
182
182
  tslib_1.__decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"assistant-run.js","sourceRoot":"","sources":["../../../server/service/unattended-run/assistant-run.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * AssistantRun — one occurrence of a mode run with no person in it, and what it left (design/ai/unattended-run.md).\n *\n * The one place the facts of an AI run are kept: its attempts, its state, the draft, the reads the draft rested on,\n * what it spent and the doors behind it. The application that scheduled it keeps its own facts and points here by\n * (source, subject, scheduledAt) or by id - it does not copy these (chief architect ruling, 2026-10-20).\n */\nimport { Column, CreateDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId, UpdateDateColumn } from 'typeorm'\nimport { Field, ID, Int, ObjectType } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { config } from '@things-factory/env'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\nconst LONG_TEXT = {\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined,\n nullable: true\n} as const\n\nconst parsed = (text?: string | null) => {\n if (!text) return null\n try {\n return JSON.parse(text)\n } catch {\n return null\n }\n}\n\n@Entity()\n/* One occurrence, one row - two first calls at once make one row and the other reads it (§5). */\n@Index('ix_assistant_run_0', (run: AssistantRun) => [run.domain, run.source, run.subject, run.scheduledAt], { unique: true })\n@Index('ix_assistant_run_1', (run: AssistantRun) => [run.domain, run.source, run.subject, run.createdAt])\n@ObjectType({ description: 'One scheduled run of an assistant mode with no person in it, and the draft it left.' })\nexport class AssistantRun {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Run id.' })\n readonly id?: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'The domain the run is in.' })\n domain?: Domain\n\n @RelationId((run: AssistantRun) => run.domain)\n domainId?: string\n\n @Column()\n @Field({ description: 'Readable name of the occurrence - source, subject and time.' })\n name: string\n\n @Column({ type: 'text', nullable: true })\n @Field({ nullable: true, description: 'Description.' })\n description?: string\n\n @Column({ length: 64 })\n @Field({ description: \"Who scheduled it - '<app>.<work>', e.g. 'office.strategy-review'.\" })\n source: string\n\n @Column({ length: 64 })\n @Field({ description: \"What it is about in the source's terms, e.g. a policy id.\" })\n subject: string\n\n @Column()\n @Field({ description: 'The occurrence this run is.' })\n scheduledAt: Date\n\n @Column({ length: 64 })\n @Field({ description: 'The assistant mode that ran.' })\n modeKind: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'What the mode was about.' })\n modeTarget?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'The person the run acted as - its privileges and doors were theirs.' })\n runAs?: User\n\n @RelationId((run: AssistantRun) => run.runAs)\n runAsId?: string\n\n @Column({ length: 16 })\n @Field({ description: \"'running' | 'succeeded' | 'failed' | 'refused'.\" })\n status: string\n\n @Column({ type: 'int', default: 1 })\n @Field(type => Int, { description: 'Which attempt at this occurrence the row holds, from 1.' })\n attempt: number\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When this attempt started.' })\n startedAt?: Date\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Past this a running attempt is taken as gone and the next call takes the occurrence over.' })\n deadlineAt?: Date\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When this attempt ended.' })\n finishedAt?: Date\n\n @Column(LONG_TEXT as any)\n @Field({ nullable: true, description: 'The draft - only when the run succeeded.' })\n draft?: string\n\n /** Each call of the run as the audit digests it (`ToolCallRecord`, never raw values), as JSON. */\n @Column(LONG_TEXT as any)\n reads?: string\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'The reads the draft rested on: tool, category, ids, rows, hash, ok or the error code.' })\n get readsJson(): any {\n return parsed(this.reads)\n }\n\n @Column({ type: 'simple-array', nullable: true })\n @Field(() => [String], { nullable: true, description: 'GraphQL doors the reads went through. A reader of the draft must pass all of them.' })\n doors?: string[]\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Why it failed or was refused - a code; the screen says it.' })\n outcomeCode?: string\n\n @Column({ type: 'text', nullable: true })\n outcomeParams?: string\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'Values of the outcome code.' })\n get outcomeParamsJson(): any {\n return parsed(this.outcomeParams)\n }\n\n /** The earlier attempts' endings, `[{ attempt, status, outcomeCode, startedAt, finishedAt }]`, as JSON. */\n @Column({ type: 'text', nullable: true })\n attempts?: string\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'How the earlier attempts ended.' })\n get attemptsJson(): any {\n return parsed(this.attempts)\n }\n\n @Column({ type: 'varchar', length: 1024, nullable: true })\n groundingWarnings?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The AI agent that answered.' })\n agentId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'provider:model.' })\n clientId?: string\n\n @Column({ type: 'int', default: 0 })\n @Field(type => Int, { description: 'Model requests of this attempt.' })\n modelCalls: number\n\n @Column({ type: 'int', default: 0 })\n @Field(type => Int, { description: 'Input tokens of this attempt.' })\n inputTokens: number\n\n @Column({ type: 'int', default: 0 })\n @Field(type => Int, { description: 'Output tokens of this attempt.' })\n outputTokens: number\n\n @Column({ type: 'int', default: 0 })\n @Field(type => Int, { description: 'Cards a read offered that were not kept - there was no person to press them.' })\n cardsDropped: number\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Creator.' })\n creator?: User\n\n @RelationId((run: AssistantRun) => run.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Last updater.' })\n updater?: User\n\n @RelationId((run: AssistantRun) => run.updater)\n updaterId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Creation timestamp.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'Last update timestamp.' })\n updatedAt?: Date\n}\n"]}
1
+ {"version":3,"file":"assistant-run.js","sourceRoot":"","sources":["../../../server/service/unattended-run/assistant-run.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * AssistantRun — one occurrence of a mode run with no person in it, and what it left (design/ai/unattended-run.md).\n *\n * The one place the facts of an AI run are kept: its attempts, its state, the draft, the reads the draft rested on,\n * what it spent and the doors behind it. The application that scheduled it keeps its own facts and points here by\n * (source, subject, scheduledAt) or by id - it does not copy these (chief architect ruling, 2026-10-20).\n */\nimport { Column, CreateDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId, UpdateDateColumn } from 'typeorm'\nimport { Field, ID, Int, ObjectType } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { config } from '@things-factory/env'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\nconst LONG_TEXT = {\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined,\n nullable: true\n} as const\n\nconst parsed = (text?: string | null) => {\n if (!text) return null\n try {\n return JSON.parse(text)\n } catch {\n return null\n }\n}\n\n@Entity()\n/* One occurrence, one row - two first calls at once make one row and the other reads it (§5). */\n@Index('ix_assistant_run_0', (run: AssistantRun) => [run.domain, run.source, run.subject, run.scheduledAt], { unique: true })\n@Index('ix_assistant_run_1', (run: AssistantRun) => [run.domain, run.source, run.subject, run.createdAt])\n@ObjectType({ description: 'One scheduled run of an assistant mode with no person in it, and the draft it left.' })\nexport class AssistantRun {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Run id.' })\n readonly id?: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'The domain the run is in.' })\n domain?: Domain\n\n @RelationId((run: AssistantRun) => run.domain)\n domainId?: string\n\n @Column()\n @Field({ description: 'Readable name of the occurrence - source, subject and time.' })\n name: string\n\n @Column({ type: 'text', nullable: true })\n @Field({ nullable: true, description: 'Description.' })\n description?: string\n\n @Column({ length: 64 })\n @Field({ description: \"Who scheduled it - '<app>.<work>', e.g. 'office.strategy-review'.\" })\n source: string\n\n @Column({ length: 64 })\n @Field({ description: \"What it is about in the source's terms, e.g. a policy id.\" })\n subject: string\n\n @Column()\n @Field({ description: 'The occurrence this run is.' })\n scheduledAt: Date\n\n @Column({ length: 64 })\n @Field({ description: 'The assistant mode that ran.' })\n modeKind: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'What the mode was about.' })\n modeTarget?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'The person the run acted as - its privileges and doors were theirs.' })\n runAs?: User\n\n @RelationId((run: AssistantRun) => run.runAs)\n runAsId?: string\n\n @Column({ length: 16 })\n @Field({ description: \"'running' | 'succeeded' | 'failed' | 'refused'.\" })\n status: string\n\n @Column({ type: 'int', default: 1 })\n @Field(type => Int, { description: 'Which attempt at this occurrence the row holds, from 1.' })\n attempt: number\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When this attempt started.' })\n startedAt?: Date\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Past this a running attempt is taken as gone and the next call takes the occurrence over.' })\n deadlineAt?: Date\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When this attempt ended.' })\n finishedAt?: Date\n\n @Column(LONG_TEXT as any)\n @Field({ nullable: true, description: 'The draft - only when the run succeeded.' })\n draft?: string\n\n /** Each call of the run as the audit digests it (`ToolCallRecord`, never raw values), as JSON. */\n @Column(LONG_TEXT as any)\n reads?: string\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'The reads the draft rested on: tool, category, ids, rows, hash, ok or the error code.' })\n get readsJson(): any {\n return parsed(this.reads)\n }\n\n @Column({ type: 'simple-array', nullable: true })\n @Field(() => [String], { nullable: true, description: 'GraphQL doors the reads went through. A reader of the draft must pass all of them.' })\n doors?: string[]\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Why it failed or was refused - a code; the screen says it.' })\n outcomeCode?: string\n\n @Column({ type: 'text', nullable: true })\n outcomeParams?: string\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'Values of the outcome code.' })\n get outcomeParamsJson(): any {\n return parsed(this.outcomeParams)\n }\n\n /** The earlier attempts' endings, `[{ attempt, status, outcomeCode, startedAt, finishedAt }]`, as JSON. */\n @Column({ type: 'text', nullable: true })\n attempts?: string\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'How the earlier attempts ended.' })\n get attemptsJson(): any {\n return parsed(this.attempts)\n }\n\n @Column({ type: 'text', nullable: true })\n groundingWarnings?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The AI agent that answered.' })\n agentId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'provider:model.' })\n clientId?: string\n\n @Column({ type: 'int', default: 0 })\n @Field(type => Int, { description: 'Model requests of this attempt.' })\n modelCalls: number\n\n @Column({ type: 'int', default: 0 })\n @Field(type => Int, { description: 'Input tokens of this attempt.' })\n inputTokens: number\n\n @Column({ type: 'int', default: 0 })\n @Field(type => Int, { description: 'Output tokens of this attempt.' })\n outputTokens: number\n\n @Column({ type: 'int', default: 0 })\n @Field(type => Int, { description: 'Cards a read offered that were not kept - there was no person to press them.' })\n cardsDropped: number\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Creator.' })\n creator?: User\n\n @RelationId((run: AssistantRun) => run.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Last updater.' })\n updater?: User\n\n @RelationId((run: AssistantRun) => run.updater)\n updaterId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Creation timestamp.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'Last update timestamp.' })\n updatedAt?: Date\n}\n"]}
@@ -200,7 +200,7 @@ async function runAssistantUnattended(input, deps = defaultDeps()) {
200
200
  draft: result.baseText,
201
201
  doors,
202
202
  cardsDropped: result.loop.proposals?.length ?? 0,
203
- groundingWarnings: result.groundingWarnings?.length ? JSON.stringify(result.groundingWarnings).slice(0, 1024) : null,
203
+ groundingWarnings: result.groundingWarnings?.length ? JSON.stringify(result.groundingWarnings) : null,
204
204
  ...spent()
205
205
  });
206
206
  }
@@ -1 +1 @@
1
- {"version":3,"file":"run-unattended.js","sourceRoot":"","sources":["../../../server/service/unattended-run/run-unattended.ts"],"names":[],"mappings":";;AA6GA,wDAiIC;AAGD,4CAMC;AAvPD;;;;;;;;;;;;;;GAcG;AACH,mEAAoL;AACpL,yDAAuF;AACvF,iDAAqD;AAErD,+DAAgE;AAChE,6EAA+E;AAC/E,uEAAiE;AACjE,kEAA6D;AAC7D,sDAAiD;AACjD,yDAAiD;AACjD,2CAAiE;AACjE,mDAAmE;AACnE,2CAA0D;AAC1D,iDAA+E;AAC/E,iEAAyD;AAkCzD,MAAM,GAAG,GAAG,oCAAoC,CAAA;AAEhD,SAAS,OAAO,CAAC,KAAa;IAC5B,MAAM,KAAK,GAAQ,IAAI,KAAK,CAAC,4BAA4B,KAAK,4BAA4B,CAAC,CAAA;IAC3F,KAAK,CAAC,IAAI,GAAG,6BAA6B,CAAA;IAC1C,KAAK,CAAC,MAAM,GAAG,EAAE,KAAK,EAAE,CAAA;IACxB,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,UAAU,CAAC,KAAyB;IAC3C,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;QAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC/C,IAAI,CAAC,KAAK,CAAC,KAAK;QAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,EAAE;QAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAA;IAC3G,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,EAAE;QAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAA;IAClH,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,YAAY,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;QAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACnI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI;QAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAA;IACjD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;QAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAA;AAC3E,CAAC;AAED,SAAS,WAAW;IAClB,OAAO;QACL,KAAK,EAAE,IAAA,8BAAe,EAAC,GAAG,EAAE,CAAC,IAAA,qBAAa,EAAC,+BAAY,CAAC,CAAC;QACzD,KAAK,EAAE;YACL,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,IAAA,qBAAa,EAAC,gBAAI,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;YAC9D,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,IAAA,gCAAoB,EAAC,IAAI,EAAE,MAAM,CAAC;YAChE,kHAAkH;YAClH,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,IAAA,2BAAe,EAAC,IAAW,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;SACvF;QACD,SAAS,EAAE,4BAAW;QACtB,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,IAAA,8BAAa,EAAC,EAAE,KAAK,EAAE,CAAC;QAC7C,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE;KACtB,CAAA;AACH,CAAC;AAED,sFAAsF;AACtF,SAAS,SAAS,CAAC,IAAS,EAAE,QAAgB;IAC5C,MAAM,KAAK,GAAG,IAAI,EAAE,WAAW,CAAA;IAC/B,IAAI,KAAK,EAAE,IAAI,KAAK,gBAAgB;QAAE,OAAO,EAAE,IAAI,EAAE,0BAA0B,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,CAAA;IAChH,IAAI,KAAK,EAAE,IAAI,KAAK,gBAAgB;QAAE,OAAO,EAAE,IAAI,EAAE,2BAA2B,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAA;IAClJ,IAAI,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,2BAA2B,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,CAAA;IACxF,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,2BAA2B,EAAE,CAAA;IAClE,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,CAAC,CAAA;AAE9B,KAAK,UAAU,sBAAsB,CAAC,KAAyB,EAAE,OAA0B,WAAW,EAAE;IAC7G,UAAU,CAAC,KAAK,CAAC,CAAA;IACjB,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,CAAA;IAC7B,MAAM,IAAI,GAAG,IAAA,8BAAgB,EAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC9C,MAAM,MAAM,GAAG,IAAA,4BAAgB,EAAC,IAAI,EAAE,UAAU,CAAC,CAAA;IACjD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IAC5B,qFAAqF;IACrF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IAEtD,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAC7C,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,EAC/F;QACE,IAAI,EAAE,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,WAAW,CAAC,WAAW,EAAE,EAAE;QACrE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI;QACzB,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM;QAC7B,OAAO,EAAE,OAAO,EAAE,EAAE;QACpB,SAAS;QACT,UAAU,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,SAAS,GAAG,6BAAiB,CAAC;KACjF,EACD,SAAS,CACV,CAAA;IACD,mHAAmH;IACnH,IAAI,CAAC,OAAO;QAAE,OAAO,GAAG,CAAA;IAExB,MAAM,IAAI,GAAG,EAAE,EAAE,EAAE,GAAG,CAAC,EAAG,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAA;IAClD,MAAM,GAAG,GAAG,CAAC,MAAiB,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,MAAM,EAAE,CAAC,CAAA;IACjG,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,MAAY,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAS,CAAC,CAAA;IAC1J,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,MAAY,EAAE,KAAiB,EAAE,EAAE,CAC7D,GAAG,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,KAAK,EAAS,CAAC,CAAA;IAEtH,IAAI,CAAC,IAAI;QAAE,OAAO,MAAM,CAAC,wBAAwB,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;IAC7E,IAAI,CAAC,IAAI,CAAC,UAAU;QAAE,OAAO,MAAM,CAAC,+BAA+B,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;IAEzF,MAAM,MAAM,GAAG,MAAM,IAAA,sBAAU,EAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;IAC5D,IAAI,SAAS,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;IAC/E,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAA;IAExB,IAAI,WAAwB,CAAA;IAC5B,IAAI,CAAC;QACH,WAAW,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;IAC1F,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,2GAA2G;QAC3G,IAAI,KAAK,EAAE,IAAI;YAAE,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;QAChE,OAAO,IAAI,CAAC,2BAA2B,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;IACtG,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,IAAA,8BAAc,EAAC,WAAW,EAAE,MAAM,CAAC,CAAA;IAC7D,MAAM,KAAK,GAAqB,EAAE,CAAA;IAClC,IAAI,IAAI,GAAG,KAAK,CAAA;IAChB,MAAM,KAAK,GAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,EAAE,CAAA;IACnG,MAAM,UAAU,GAAG,IAAA,6CAAwB,EAAC,IAAI,CAAC,CAAA;IACjD,MAAM,YAAY,GAAG,IAAA,uCAAgB,EAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;IACnE,MAAM,QAAQ,GAAG,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,OAAO,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAA;IAExE,MAAM,KAAK,GAAG,IAAI,eAAe,EAAE,CAAA;IACnC,IAAI,KAAgD,CAAA;IACpD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAmB,OAAO,CAAC,EAAE;QACtD,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YACtB,KAAK,CAAC,KAAK,EAAE,CAAA;YACb,OAAO,CAAC,SAAS,CAAC,CAAA;QACpB,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,CAAA;IACtB,CAAC,CAAC,CAAA;IAEF,MAAM,KAAK,GAAG,GAAc,EAAE,CAAC,CAAC;QAC9B,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;QAC5B,OAAO,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE;QAC7B,QAAQ,EAAE,WAAW,CAAC,EAAE;QACxB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,YAAY,EAAE,KAAK,CAAC,YAAY;KACjC,CAAC,CAAA;IAEF,IAAI,MAAW,CAAA;IACf,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;YAC1B,IAAA,sCAAqB,EAAC;gBACpB,MAAM;gBACN,QAAQ;gBACR,YAAY;gBACZ,cAAc,EAAE,UAAU;gBAC1B,6HAA6H;gBAC7H,KAAK,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,cAAc,IAAI,EAAE,EAAE;gBAChD,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;gBACtF,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;gBAClC,WAAW,EAAE,KAAK,CAAC,WAAW;gBAC9B,KAAK;gBACL,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,sBAAsB,EAAE,IAAI,CAAC,sBAAsB;gBACnD,iHAAiH;gBACjH,aAAa,EAAE,MAAM,CAAC,aAAa;gBACnC,sBAAsB,EAAE,KAAK;gBAC7B,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,UAAU,EAAE,MAAM,CAAC,EAAE;oBACnB,IAAI,CAAC,IAAI;wBAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;gBAC/B,CAAC;gBACD,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,CAAC,WAAW,EAAE,EAAE;gBAC1G,gBAAgB,EAAE,IAAA,0CAAmB,EAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAe,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;aACnH,CAAC;YACF,OAAO;SACR,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,IAAI,GAAG,IAAI,CAAA;QACX,YAAY,CAAC,KAAK,CAAC,CAAA;QACnB,IAAI,KAAK,YAAY,iCAAiB;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;QACtF,OAAO,IAAI,CAAC,2BAA2B,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;IAC/G,CAAC;IACD,IAAI,GAAG,IAAI,CAAA;IACX,YAAY,CAAC,KAAK,CAAC,CAAA;IAEnB,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,uBAAuB,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;IACxG,iGAAiG;IACjG,IAAI,KAAK,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC,+BAA+B,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;IAEzF,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAA;IAC7D,IAAI,OAAO;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;IAE/D,MAAM,KAAK,GAAG,IAAA,iCAAc,EAC1B,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAU,EAAE,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAC/E,IAAI,CAAC,EAAE,CAAE,IAAA,6BAAY,EAAC,IAAI,EAAE,UAAU,CAAS,EAAE,KAAK,CACvD,CAAA;IACD,OAAO,GAAG,CAAC;QACT,MAAM,EAAE,WAAW;QACnB,KAAK,EAAE,MAAM,CAAC,QAAQ;QACtB,KAAK;QACL,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC;QAChD,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;QACpH,GAAG,KAAK,EAAE;KACJ,CAAC,CAAA;AACX,CAAC;AAED,wHAAwH;AACjH,KAAK,UAAU,gBAAgB,CAAC,QAAmG;IACxI,MAAM,UAAU,GAAG,IAAA,qBAAa,EAAC,+BAAY,CAAC,CAAA;IAC9C,IAAI,IAAI,IAAI,QAAQ;QAAE,OAAO,UAAU,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;IAC/E,OAAO,UAAU,CAAC,OAAO,CAAC;QACxB,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAS;KAC3I,CAAC,CAAA;AACJ,CAAC","sourcesContent":["/*\n * Run an assistant mode with no person in it, for scheduled work, and keep what it wrote as a draft\n * (operato-application/design/ai/unattended-run.md, ADR-0142 §8.1, user ruling C 2026-10-20).\n *\n * A server function and no GraphQL door: a door would let a person name someone else as `runAs`. The caller is a\n * server's scheduled task - Office's periodic strategy review is the first.\n *\n * identity (domain, source, subject, scheduledAt) is one row - a retry hands the stored run back (§5)\n * who the run-as user, judged again now; refused when they cannot ask the assistant themselves (§2)\n * tools the mode's read tools behind that user's own doors; no 입력안, no cards kept (§3)\n * limits the mode's: model requests, tokens, wall time (§4)\n * outcome succeeded with a draft, failed or refused with a code - never an empty success (§7)\n *\n * Every tool call goes through the registry door, so the recorders write it down as they do a turn's (§6).\n */\nimport { aiClientFor, findToolSpec, registryAssistantChat, type AgentClient, type AiClientRequest, type ToolAccessCheck, type ToolCallRecord } from '@things-factory/ai-client-base'\nimport { User, canUserCheckinDomain, checkPermission } from '@things-factory/auth-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { getAssistantMode } from '../assistant-mode/registry.js'\nimport { unattendedToolCategories } from '../assistant-mode/mode-categories.js'\nimport { doorsForAnswer } from '../chat-message/message-doors.js'\nimport { groundingSourcesFor } from '../grounding-sources.js'\nimport { toolAccessFor } from '../tool-access.js'\nimport { AssistantRun } from './assistant-run.js'\nimport { ABANDON_MARGIN_MS, unattendedLimits } from './limits.js'\nimport { budgetedClient, RunBudgetExceeded } from './run-budget.js'\nimport { judgeRunAs, type RunAsChecks } from './run-as.js'\nimport { typeormRunStore, type RunEnding, type RunStore } from './run-store.js'\nimport { unattendedPrompt } from './unattended-prompt.js'\n\nexport interface UnattendedRunInput {\n /** The domain the run is in, as loaded - its parent and owner decide who can enter it. */\n domain: { id: string; [key: string]: any }\n /** The user the run acts as - the policy owner. Their privileges and doors only. */\n runAs: string\n /** Which occurrence this is. The same three again is the same run. */\n run: {\n /** Who scheduled it - `<app>.<work>`, e.g. `office.strategy-review`. */\n source: string\n /** What it is about in the source's terms - a policy id. */\n subject: string\n scheduledAt: Date\n }\n /** A registered mode that declares `unattended`. */\n mode: { kind: string; target?: string }\n /** What this occurrence is asked to do - the caller server's text, read by the model (English). */\n instruction: string\n /** What the run is about, for the tools. Filled by the caller's server; still never used for authorization. */\n hostContext?: Record<string, any>\n /** The language of the draft - the owner's. Absent: their account's locale. */\n lng?: string\n}\n\n/** What the run reaches outside itself. The defaults are the server's; the tests hand their own. */\nexport interface UnattendedRunDeps {\n store: RunStore\n runAs: RunAsChecks<any, any>\n clientFor: (request: AiClientRequest) => Promise<AgentClient>\n toolAccess: (state: any) => ToolAccessCheck\n now: () => Date\n}\n\nconst KEY = /^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$/\n\nfunction invalid(field: string): Error {\n const error: any = new Error(`runAssistantUnattended: '${field}' is missing or not usable`)\n error.code = 'assistant-run-input-invalid'\n error.params = { field }\n return error\n}\n\nfunction checkInput(input: UnattendedRunInput) {\n if (!input?.domain?.id) throw invalid('domain')\n if (!input.runAs) throw invalid('runAs')\n if (!KEY.test(String(input.run?.source ?? '')) || input.run.source.length > 64) throw invalid('run.source')\n if (!String(input.run.subject ?? '').trim() || String(input.run.subject).length > 64) throw invalid('run.subject')\n if (!(input.run.scheduledAt instanceof Date) || !Number.isFinite(input.run.scheduledAt.valueOf())) throw invalid('run.scheduledAt')\n if (!input.mode?.kind) throw invalid('mode.kind')\n if (!String(input.instruction ?? '').trim()) throw invalid('instruction')\n}\n\nfunction defaultDeps(): UnattendedRunDeps {\n return {\n store: typeormRunStore(() => getRepository(AssistantRun)),\n runAs: {\n findUser: id => getRepository(User).findOne({ where: { id } }),\n mayCheckin: (user, domain) => canUserCheckinDomain(user, domain),\n /* No request: no address to refuse from, and no license prohibition - those cover writes this run never makes. */\n permits: (gate, user, domain) => checkPermission(gate as any, user, domain, false, [])\n },\n clientFor: aiClientFor,\n toolAccess: state => toolAccessFor({ state }),\n now: () => new Date()\n }\n}\n\n/** Why the loop stopped, as the run's code. Only a clean end with text is a draft. */\nfunction failureOf(loop: any, baseText: string): { code: string; params?: any } | undefined {\n const abort = loop?.abortReason\n if (abort?.type === 'max_iterations') return { code: 'assistant-run-step-limit', params: { steps: abort.iter } }\n if (abort?.type === 'provider_error') return { code: 'assistant-run-model-error', params: { message: String(abort.message ?? '').slice(0, 500) } }\n if (abort) return { code: 'assistant-run-model-error', params: { stopped: abort.type } }\n if (!baseText.trim()) return { code: 'assistant-run-empty-draft' }\n return undefined\n}\n\nconst TIMED_OUT = Symbol('timed-out')\n\nexport async function runAssistantUnattended(input: UnattendedRunInput, deps: UnattendedRunDeps = defaultDeps()): Promise<AssistantRun> {\n checkInput(input)\n const { domain, run } = input\n const mode = getAssistantMode(input.mode.kind)\n const limits = unattendedLimits(mode?.unattended)\n const startedAt = deps.now()\n /* The account as stored now - read once, and judged after the occurrence is held. */\n const account = await deps.runAs.findUser(input.runAs)\n\n const { claimed, row } = await deps.store.claim(\n { domainId: domain.id, source: run.source, subject: run.subject, scheduledAt: run.scheduledAt },\n {\n name: `${run.source} ${run.subject} ${run.scheduledAt.toISOString()}`,\n modeKind: input.mode.kind,\n modeTarget: input.mode.target,\n runAsId: account?.id,\n startedAt,\n deadlineAt: new Date(startedAt.valueOf() + limits.timeoutMs + ABANDON_MARGIN_MS)\n },\n startedAt\n )\n /* Succeeded, or running elsewhere and still alive - the caller gets that row and the model is not called again. */\n if (!claimed) return row\n\n const held = { id: row.id!, attempt: row.attempt }\n const end = (ending: RunEnding) => deps.store.finish(held, { finishedAt: deps.now(), ...ending })\n const refuse = (code: string, params?: any) => end({ status: 'refused', outcomeCode: code, outcomeParams: params ? JSON.stringify(params) : null } as any)\n const fail = (code: string, params?: any, spent?: RunEnding) =>\n end({ status: 'failed', outcomeCode: code, outcomeParams: params ? JSON.stringify(params) : null, ...spent } as any)\n\n if (!mode) return refuse('unknown-assistant-mode', { kind: input.mode.kind })\n if (!mode.unattended) return refuse('assistant-mode-not-unattended', { kind: mode.kind })\n\n const judged = await judgeRunAs(account, domain, deps.runAs)\n if ('refused' in judged) return refuse(judged.refused, { userId: input.runAs })\n const user = judged.user\n\n let agentClient: AgentClient\n try {\n agentClient = await deps.clientFor({ domain: domain as any, user, modeKind: mode.kind })\n } catch (error: any) {\n /* No AI here, or not the agent asked for - nothing ran. Its own code, as the conversation door says it. */\n if (error?.code) return refuse(String(error.code), error.params)\n return fail('assistant-run-model-error', { message: String(error?.message ?? error).slice(0, 500) })\n }\n\n const { client, spend } = budgetedClient(agentClient, limits)\n const reads: ToolCallRecord[] = []\n let over = false\n const state: any = { domain, user, lng: input.lng ?? user.locale, aiAgentId: agentClient.agent.id }\n const categories = unattendedToolCategories(mode)\n const systemPrompt = unattendedPrompt(mode.systemPrompt, state.lng)\n const messages = [{ role: 'user' as const, content: input.instruction }]\n\n const abort = new AbortController()\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<typeof TIMED_OUT>(resolve => {\n timer = setTimeout(() => {\n abort.abort()\n resolve(TIMED_OUT)\n }, limits.timeoutMs)\n })\n\n const spent = (): RunEnding => ({\n reads: JSON.stringify(reads),\n agentId: agentClient.agent.id,\n clientId: agentClient.id,\n modelCalls: spend.modelCalls,\n inputTokens: spend.inputTokens,\n outputTokens: spend.outputTokens\n })\n\n let result: any\n try {\n result = await Promise.race([\n registryAssistantChat({\n client,\n messages,\n systemPrompt,\n toolCategories: categories,\n /* No screen and no conversation - the mode's own tools in front, the lists opened as the run needs (ADR-0061 decision 9). */\n front: { categories: mode.toolCategories ?? [] },\n mode: { kind: mode.kind, ...(input.mode.target ? { target: input.mode.target } : {}) },\n toolAccess: deps.toolAccess(state),\n hostContext: input.hostContext,\n state,\n temperature: mode.temperature,\n maxTokens: mode.maxTokens,\n requireToolOnFirstTurn: mode.requireToolOnFirstTurn,\n /* Each step is one request. A run that reaches the last step without an answer failed - no wrap-up call (§4). */\n maxIterations: limits.maxModelCalls,\n salvageOnMaxIterations: false,\n signal: abort.signal,\n onToolCall: record => {\n if (!over) reads.push(record)\n },\n run: { id: held.id, source: run.source, subject: run.subject, scheduledAt: run.scheduledAt.toISOString() },\n groundingSources: groundingSourcesFor({ systemPrompt, messages: messages as any, hostContext: input.hostContext })\n }),\n timeout\n ])\n } catch (error: any) {\n over = true\n clearTimeout(timer)\n if (error instanceof RunBudgetExceeded) return fail(error.code, error.params, spent())\n return fail('assistant-run-model-error', { message: String(error?.message ?? error).slice(0, 500) }, spent())\n }\n over = true\n clearTimeout(timer)\n\n if (result === TIMED_OUT) return fail('assistant-run-timeout', { timeoutMs: limits.timeoutMs }, spent())\n /* The loop turns a refused request into a provider error; the budget says which limit it was. */\n if (spend.exceeded) return fail('assistant-run-budget-exceeded', spend.exceeded, spent())\n\n const failure = failureOf(result.loop, result.baseText ?? '')\n if (failure) return fail(failure.code, failure.params, spent())\n\n const doors = doorsForAnswer(\n (result.loop.toolUsages ?? []).map((usage: any) => usage?.name).filter(Boolean),\n name => (findToolSpec(name, categories) as any)?.doors\n )\n return end({\n status: 'succeeded',\n draft: result.baseText,\n doors,\n cardsDropped: result.loop.proposals?.length ?? 0,\n groundingWarnings: result.groundingWarnings?.length ? JSON.stringify(result.groundingWarnings).slice(0, 1024) : null,\n ...spent()\n } as any)\n}\n\n/** The stored run of an occurrence, for the application that scheduled it - read behind that application's own door. */\nexport async function findAssistantRun(identity: { domainId: string; source: string; subject: string; scheduledAt: Date } | { id: string }): Promise<AssistantRun | null> {\n const repository = getRepository(AssistantRun)\n if ('id' in identity) return repository.findOne({ where: { id: identity.id } })\n return repository.findOne({\n where: { domain: { id: identity.domainId }, source: identity.source, subject: identity.subject, scheduledAt: identity.scheduledAt } as any\n })\n}\n"]}
1
+ {"version":3,"file":"run-unattended.js","sourceRoot":"","sources":["../../../server/service/unattended-run/run-unattended.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Run an assistant mode with no person in it, for scheduled work, and keep what it wrote as a draft\n * (operato-application/design/ai/unattended-run.md, ADR-0142 §8.1, user ruling C 2026-10-20).\n *\n * A server function and no GraphQL door: a door would let a person name someone else as `runAs`. The caller is a\n * server's scheduled task - Office's periodic strategy review is the first.\n *\n * identity (domain, source, subject, scheduledAt) is one row - a retry hands the stored run back (§5)\n * who the run-as user, judged again now; refused when they cannot ask the assistant themselves (§2)\n * tools the mode's read tools behind that user's own doors; no 입력안, no cards kept (§3)\n * limits the mode's: model requests, tokens, wall time (§4)\n * outcome succeeded with a draft, failed or refused with a code - never an empty success (§7)\n *\n * Every tool call goes through the registry door, so the recorders write it down as they do a turn's (§6).\n */\nimport { aiClientFor, findToolSpec, registryAssistantChat, type AgentClient, type AiClientRequest, type ToolAccessCheck, type ToolCallRecord } from '@things-factory/ai-client-base'\nimport { User, canUserCheckinDomain, checkPermission } from '@things-factory/auth-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { getAssistantMode } from '../assistant-mode/registry.js'\nimport { unattendedToolCategories } from '../assistant-mode/mode-categories.js'\nimport { doorsForAnswer } from '../chat-message/message-doors.js'\nimport { groundingSourcesFor } from '../grounding-sources.js'\nimport { toolAccessFor } from '../tool-access.js'\nimport { AssistantRun } from './assistant-run.js'\nimport { ABANDON_MARGIN_MS, unattendedLimits } from './limits.js'\nimport { budgetedClient, RunBudgetExceeded } from './run-budget.js'\nimport { judgeRunAs, type RunAsChecks } from './run-as.js'\nimport { typeormRunStore, type RunEnding, type RunStore } from './run-store.js'\nimport { unattendedPrompt } from './unattended-prompt.js'\n\nexport interface UnattendedRunInput {\n /** The domain the run is in, as loaded - its parent and owner decide who can enter it. */\n domain: { id: string; [key: string]: any }\n /** The user the run acts as - the policy owner. Their privileges and doors only. */\n runAs: string\n /** Which occurrence this is. The same three again is the same run. */\n run: {\n /** Who scheduled it - `<app>.<work>`, e.g. `office.strategy-review`. */\n source: string\n /** What it is about in the source's terms - a policy id. */\n subject: string\n scheduledAt: Date\n }\n /** A registered mode that declares `unattended`. */\n mode: { kind: string; target?: string }\n /** What this occurrence is asked to do - the caller server's text, read by the model (English). */\n instruction: string\n /** What the run is about, for the tools. Filled by the caller's server; still never used for authorization. */\n hostContext?: Record<string, any>\n /** The language of the draft - the owner's. Absent: their account's locale. */\n lng?: string\n}\n\n/** What the run reaches outside itself. The defaults are the server's; the tests hand their own. */\nexport interface UnattendedRunDeps {\n store: RunStore\n runAs: RunAsChecks<any, any>\n clientFor: (request: AiClientRequest) => Promise<AgentClient>\n toolAccess: (state: any) => ToolAccessCheck\n now: () => Date\n}\n\nconst KEY = /^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$/\n\nfunction invalid(field: string): Error {\n const error: any = new Error(`runAssistantUnattended: '${field}' is missing or not usable`)\n error.code = 'assistant-run-input-invalid'\n error.params = { field }\n return error\n}\n\nfunction checkInput(input: UnattendedRunInput) {\n if (!input?.domain?.id) throw invalid('domain')\n if (!input.runAs) throw invalid('runAs')\n if (!KEY.test(String(input.run?.source ?? '')) || input.run.source.length > 64) throw invalid('run.source')\n if (!String(input.run.subject ?? '').trim() || String(input.run.subject).length > 64) throw invalid('run.subject')\n if (!(input.run.scheduledAt instanceof Date) || !Number.isFinite(input.run.scheduledAt.valueOf())) throw invalid('run.scheduledAt')\n if (!input.mode?.kind) throw invalid('mode.kind')\n if (!String(input.instruction ?? '').trim()) throw invalid('instruction')\n}\n\nfunction defaultDeps(): UnattendedRunDeps {\n return {\n store: typeormRunStore(() => getRepository(AssistantRun)),\n runAs: {\n findUser: id => getRepository(User).findOne({ where: { id } }),\n mayCheckin: (user, domain) => canUserCheckinDomain(user, domain),\n /* No request: no address to refuse from, and no license prohibition - those cover writes this run never makes. */\n permits: (gate, user, domain) => checkPermission(gate as any, user, domain, false, [])\n },\n clientFor: aiClientFor,\n toolAccess: state => toolAccessFor({ state }),\n now: () => new Date()\n }\n}\n\n/** Why the loop stopped, as the run's code. Only a clean end with text is a draft. */\nfunction failureOf(loop: any, baseText: string): { code: string; params?: any } | undefined {\n const abort = loop?.abortReason\n if (abort?.type === 'max_iterations') return { code: 'assistant-run-step-limit', params: { steps: abort.iter } }\n if (abort?.type === 'provider_error') return { code: 'assistant-run-model-error', params: { message: String(abort.message ?? '').slice(0, 500) } }\n if (abort) return { code: 'assistant-run-model-error', params: { stopped: abort.type } }\n if (!baseText.trim()) return { code: 'assistant-run-empty-draft' }\n return undefined\n}\n\nconst TIMED_OUT = Symbol('timed-out')\n\nexport async function runAssistantUnattended(input: UnattendedRunInput, deps: UnattendedRunDeps = defaultDeps()): Promise<AssistantRun> {\n checkInput(input)\n const { domain, run } = input\n const mode = getAssistantMode(input.mode.kind)\n const limits = unattendedLimits(mode?.unattended)\n const startedAt = deps.now()\n /* The account as stored now - read once, and judged after the occurrence is held. */\n const account = await deps.runAs.findUser(input.runAs)\n\n const { claimed, row } = await deps.store.claim(\n { domainId: domain.id, source: run.source, subject: run.subject, scheduledAt: run.scheduledAt },\n {\n name: `${run.source} ${run.subject} ${run.scheduledAt.toISOString()}`,\n modeKind: input.mode.kind,\n modeTarget: input.mode.target,\n runAsId: account?.id,\n startedAt,\n deadlineAt: new Date(startedAt.valueOf() + limits.timeoutMs + ABANDON_MARGIN_MS)\n },\n startedAt\n )\n /* Succeeded, or running elsewhere and still alive - the caller gets that row and the model is not called again. */\n if (!claimed) return row\n\n const held = { id: row.id!, attempt: row.attempt }\n const end = (ending: RunEnding) => deps.store.finish(held, { finishedAt: deps.now(), ...ending })\n const refuse = (code: string, params?: any) => end({ status: 'refused', outcomeCode: code, outcomeParams: params ? JSON.stringify(params) : null } as any)\n const fail = (code: string, params?: any, spent?: RunEnding) =>\n end({ status: 'failed', outcomeCode: code, outcomeParams: params ? JSON.stringify(params) : null, ...spent } as any)\n\n if (!mode) return refuse('unknown-assistant-mode', { kind: input.mode.kind })\n if (!mode.unattended) return refuse('assistant-mode-not-unattended', { kind: mode.kind })\n\n const judged = await judgeRunAs(account, domain, deps.runAs)\n if ('refused' in judged) return refuse(judged.refused, { userId: input.runAs })\n const user = judged.user\n\n let agentClient: AgentClient\n try {\n agentClient = await deps.clientFor({ domain: domain as any, user, modeKind: mode.kind })\n } catch (error: any) {\n /* No AI here, or not the agent asked for - nothing ran. Its own code, as the conversation door says it. */\n if (error?.code) return refuse(String(error.code), error.params)\n return fail('assistant-run-model-error', { message: String(error?.message ?? error).slice(0, 500) })\n }\n\n const { client, spend } = budgetedClient(agentClient, limits)\n const reads: ToolCallRecord[] = []\n let over = false\n const state: any = { domain, user, lng: input.lng ?? user.locale, aiAgentId: agentClient.agent.id }\n const categories = unattendedToolCategories(mode)\n const systemPrompt = unattendedPrompt(mode.systemPrompt, state.lng)\n const messages = [{ role: 'user' as const, content: input.instruction }]\n\n const abort = new AbortController()\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<typeof TIMED_OUT>(resolve => {\n timer = setTimeout(() => {\n abort.abort()\n resolve(TIMED_OUT)\n }, limits.timeoutMs)\n })\n\n const spent = (): RunEnding => ({\n reads: JSON.stringify(reads),\n agentId: agentClient.agent.id,\n clientId: agentClient.id,\n modelCalls: spend.modelCalls,\n inputTokens: spend.inputTokens,\n outputTokens: spend.outputTokens\n })\n\n let result: any\n try {\n result = await Promise.race([\n registryAssistantChat({\n client,\n messages,\n systemPrompt,\n toolCategories: categories,\n /* No screen and no conversation - the mode's own tools in front, the lists opened as the run needs (ADR-0061 decision 9). */\n front: { categories: mode.toolCategories ?? [] },\n mode: { kind: mode.kind, ...(input.mode.target ? { target: input.mode.target } : {}) },\n toolAccess: deps.toolAccess(state),\n hostContext: input.hostContext,\n state,\n temperature: mode.temperature,\n maxTokens: mode.maxTokens,\n requireToolOnFirstTurn: mode.requireToolOnFirstTurn,\n /* Each step is one request. A run that reaches the last step without an answer failed - no wrap-up call (§4). */\n maxIterations: limits.maxModelCalls,\n salvageOnMaxIterations: false,\n signal: abort.signal,\n onToolCall: record => {\n if (!over) reads.push(record)\n },\n run: { id: held.id, source: run.source, subject: run.subject, scheduledAt: run.scheduledAt.toISOString() },\n groundingSources: groundingSourcesFor({ systemPrompt, messages: messages as any, hostContext: input.hostContext })\n }),\n timeout\n ])\n } catch (error: any) {\n over = true\n clearTimeout(timer)\n if (error instanceof RunBudgetExceeded) return fail(error.code, error.params, spent())\n return fail('assistant-run-model-error', { message: String(error?.message ?? error).slice(0, 500) }, spent())\n }\n over = true\n clearTimeout(timer)\n\n if (result === TIMED_OUT) return fail('assistant-run-timeout', { timeoutMs: limits.timeoutMs }, spent())\n /* The loop turns a refused request into a provider error; the budget says which limit it was. */\n if (spend.exceeded) return fail('assistant-run-budget-exceeded', spend.exceeded, spent())\n\n const failure = failureOf(result.loop, result.baseText ?? '')\n if (failure) return fail(failure.code, failure.params, spent())\n\n const doors = doorsForAnswer(\n (result.loop.toolUsages ?? []).map((usage: any) => usage?.name).filter(Boolean),\n name => (findToolSpec(name, categories) as any)?.doors\n )\n return end({\n status: 'succeeded',\n draft: result.baseText,\n doors,\n cardsDropped: result.loop.proposals?.length ?? 0,\n groundingWarnings: result.groundingWarnings?.length ? JSON.stringify(result.groundingWarnings) : null,\n ...spent()\n } as any)\n}\n\n/** The stored run of an occurrence, for the application that scheduled it - read behind that application's own door. */\nexport async function findAssistantRun(identity: { domainId: string; source: string; subject: string; scheduledAt: Date } | { id: string }): Promise<AssistantRun | null> {\n const repository = getRepository(AssistantRun)\n if ('id' in identity) return repository.findOne({ where: { id: identity.id } })\n return repository.findOne({\n where: { domain: { id: identity.domainId }, source: identity.source, subject: identity.subject, scheduledAt: identity.scheduledAt } as any\n })\n}\n"]}