@xfey/tutti 0.1.126 → 0.1.127
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.
- package/README.md +2 -0
- package/dist/chat-assistant/diagnostics.d.ts +4 -1
- package/dist/chat-assistant/index.js +15 -6
- package/dist/chat-assistant/language-context.d.ts +8 -0
- package/dist/chat-assistant/language-context.js +41 -0
- package/dist/collaboration-state/communication-language.d.ts +31 -0
- package/dist/collaboration-state/communication-language.js +36 -0
- package/dist/collaboration-state/task-compile-context.js +9 -0
- package/dist/communication-language/index.d.ts +75 -0
- package/dist/communication-language/index.js +122 -0
- package/dist/communication-language/live-smoke-cli.d.ts +2 -0
- package/dist/communication-language/live-smoke-cli.js +368 -0
- package/dist/communication-language/prompt.d.ts +22 -0
- package/dist/communication-language/prompt.js +73 -0
- package/dist/control-plane/communication-language.d.ts +33 -0
- package/dist/control-plane/communication-language.js +72 -0
- package/dist/control-plane/context-sync.js +7 -0
- package/dist/control-plane/follow-up-output.js +25 -4
- package/dist/control-plane/follow-up-start.js +17 -0
- package/dist/control-plane/project-brief-refresh.js +10 -1
- package/dist/control-plane/project-context-bootstrap.d.ts +2 -0
- package/dist/control-plane/project-context-bootstrap.js +8 -0
- package/dist/control-plane/reference-summary-refresh.js +7 -0
- package/dist/control-plane/run-start.js +14 -6
- package/dist/control-plane/scratchpad-refresh-start.d.ts +1 -0
- package/dist/control-plane/scratchpad-refresh-start.js +13 -0
- package/dist/control-plane/scratchpad-refresh.d.ts +2 -0
- package/dist/control-plane/scratchpad-refresh.js +40 -26
- package/dist/control-plane/task-compile-continuation.js +14 -1
- package/dist/control-plane/task-compile-output.js +38 -14
- package/dist/control-plane/task-compile-start.d.ts +1 -0
- package/dist/control-plane/task-compile-start.js +22 -1
- package/dist/control-plane/types.d.ts +2 -0
- package/dist/control-plane/workflows/openai.js +48 -28
- package/dist/control-plane/workflows/types.d.ts +18 -13
- package/dist/prompt-templates/index.d.ts +1 -1
- package/dist/prompt-templates/index.js +2 -0
- package/dist/run-pipeline/task-run-invocation.js +12 -9
- package/migrations/0023_communication_language.sql +14 -0
- package/package.json +1 -1
- package/prompts/README.md +1 -0
- package/prompts/chat-assistant.md +1 -1
- package/prompts/communication-language-result.md +27 -0
- package/prompts/communication-language.md +50 -0
- package/prompts/procedures/follow-up-check.md +1 -1
- package/prompts/procedures/scratchpad-refresh.md +2 -2
- package/prompts/procedures/task-compile.md +1 -1
- package/prompts/prompt-flow-map.md +2 -0
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# server
|
|
2
2
|
|
|
3
|
+
当前源码的[协作输出语言](../../docs/system/communication-language.md)由 Host 统一处理:migration `0023`、共享 prompt、Scratchpad/workflow/task 语言继承。通过 Host npm 包交付,需更新项目 Host 才会生效,不依赖 Web 或飞书端新增设置;对照模型的引用语言误判作为已知项保留,不宣称所有模型均稳定通过。
|
|
4
|
+
|
|
3
5
|
## 作用
|
|
4
6
|
|
|
5
7
|
`apps/server` 是 Tutti 的项目 host service,也是机器级 Local Console 的进程入口。
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { ProviderInvocationProgress } from "../providers/invocation-progress.js";
|
|
2
|
+
import type { languageDiagnosticFields } from "../communication-language/index.js";
|
|
2
3
|
export type ChatAssistantLogger = {
|
|
3
4
|
info: (fields: Record<string, unknown>, message: string) => void;
|
|
4
5
|
warn: (fields: Record<string, unknown>, message: string) => void;
|
|
5
6
|
};
|
|
6
|
-
type ChatAssistantProgress = ProviderInvocationProgress | {
|
|
7
|
+
type ChatAssistantProgress = ProviderInvocationProgress | ({
|
|
8
|
+
stage: "language_context";
|
|
9
|
+
} & ReturnType<typeof languageDiagnosticFields>) | {
|
|
7
10
|
stage: "queued" | "inspection_wait" | "model_requested" | "reply_persisted" | "failed" | "skipped";
|
|
8
11
|
reason_code?: "queue_full" | "stopping" | "context_unavailable" | "answer_failed" | "reply_failed";
|
|
9
12
|
reference_source_count?: number;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createChatAssistantDiagnostics } from "./diagnostics.js";
|
|
2
2
|
import { createClarificationRoundAgentMessage, createMainChatAgentMessage, readClarificationRoundMessagesWindow, readClarificationRoundProjection, readMainChatMessageWindow, readMainChatMessagesBeforeCursor, readMessageProjectionById, } from "../collaboration-state/index.js";
|
|
3
|
-
import {
|
|
3
|
+
import { renderLanguageAwarePrompt } from "../communication-language/prompt.js";
|
|
4
|
+
import { languageDiagnosticFields } from "../communication-language/index.js";
|
|
5
|
+
import { chatLanguageContext } from "./language-context.js";
|
|
4
6
|
import { withHostStoreTransaction, } from "../store/index.js";
|
|
5
7
|
import { messageTextForModel, messageTextForTuttiMention, } from "../collaboration-state/message-context.js";
|
|
6
8
|
import { externalReferenceMessageContext } from "../reference-access/message-context.js";
|
|
@@ -241,18 +243,25 @@ export class ReadOnlyChatAssistant {
|
|
|
241
243
|
}
|
|
242
244
|
return this.buildMainChatContext(triggerMessage);
|
|
243
245
|
}
|
|
244
|
-
renderPrompt(triggerMessage) {
|
|
246
|
+
renderPrompt(triggerMessage, report) {
|
|
245
247
|
const context = this.buildContext(triggerMessage);
|
|
246
248
|
if (context === null) {
|
|
247
249
|
return null;
|
|
248
250
|
}
|
|
249
|
-
|
|
251
|
+
const language = chatLanguageContext(this.options.store.db, triggerMessage, {
|
|
252
|
+
mainChat: CHAT_ASSISTANT_CONTEXT_MESSAGE_LIMIT,
|
|
253
|
+
round: CHAT_ASSISTANT_ROUND_MESSAGE_LIMIT,
|
|
254
|
+
});
|
|
255
|
+
report({ stage: "language_context", ...languageDiagnosticFields(language) });
|
|
256
|
+
return renderLanguageAwarePrompt({
|
|
250
257
|
templateKey: "chat.assistant",
|
|
251
|
-
...(this.options.promptsRoot === undefined
|
|
258
|
+
...(this.options.promptsRoot === undefined
|
|
259
|
+
? {}
|
|
260
|
+
: { promptsRoot: this.options.promptsRoot }),
|
|
252
261
|
variables: {
|
|
253
262
|
assistant_context_json: context,
|
|
254
263
|
},
|
|
255
|
-
});
|
|
264
|
+
}, language);
|
|
256
265
|
}
|
|
257
266
|
appendMainChatAssistantMessage(triggerMessage, body, refs) {
|
|
258
267
|
let afterCommit;
|
|
@@ -342,7 +351,7 @@ export class ReadOnlyChatAssistant {
|
|
|
342
351
|
try {
|
|
343
352
|
report({ stage: "inspection_wait" });
|
|
344
353
|
await this.options.waitForInspectionAvailability?.();
|
|
345
|
-
const prompt = this.renderPrompt(triggerMessage);
|
|
354
|
+
const prompt = this.renderPrompt(triggerMessage, report);
|
|
346
355
|
if (prompt === null) {
|
|
347
356
|
report({ stage: "skipped", reason_code: "context_unavailable" });
|
|
348
357
|
return;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MessageProjection } from "@tutti/shared/schemas/api";
|
|
2
|
+
import type { SqliteDatabase } from "../store/index.js";
|
|
3
|
+
/** Same compact scope as Q&A, never boundary-after main chat for a round. */
|
|
4
|
+
export declare function chatLanguageContext(db: SqliteDatabase, trigger: MessageProjection, limits: {
|
|
5
|
+
mainChat: number;
|
|
6
|
+
round: number;
|
|
7
|
+
}): import("../communication-language/index.js").CommunicationLanguageContext;
|
|
8
|
+
//# sourceMappingURL=language-context.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { readActiveClarificationProjection, readClarificationRoundMessagesWindow, readMainChatMessageWindow, readTaskProjection, } from "../collaboration-state/index.js";
|
|
2
|
+
import { readCommunicationLanguage } from "../collaboration-state/communication-language.js";
|
|
3
|
+
import { createLanguageContext, messageLanguageEvidence } from "../communication-language/index.js";
|
|
4
|
+
/** Same compact scope as Q&A, never boundary-after main chat for a round. */
|
|
5
|
+
export function chatLanguageContext(db, trigger, limits) {
|
|
6
|
+
let inherited = readCommunicationLanguage(db);
|
|
7
|
+
let messages;
|
|
8
|
+
if (trigger.scope.kind === "main_chat") {
|
|
9
|
+
messages = readMainChatMessageWindow(db, { limit: limits.mainChat }).items;
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
messages =
|
|
13
|
+
readClarificationRoundMessagesWindow(db, trigger.scope.clarification_round_ref, {
|
|
14
|
+
anchor: "start",
|
|
15
|
+
limit: limits.round,
|
|
16
|
+
})?.items ?? [];
|
|
17
|
+
const active = readActiveClarificationProjection(db);
|
|
18
|
+
// A task/compile round must not inherit a newer project-wide language choice.
|
|
19
|
+
inherited = null;
|
|
20
|
+
if (active.state === "active" && active.round.id === trigger.scope.clarification_round_ref) {
|
|
21
|
+
const owner = active.thread.owner;
|
|
22
|
+
const task = owner.kind === "task" ? readTaskProjection(db, owner.task_id) : null;
|
|
23
|
+
const workflowRef = owner.kind === "task_compile" ? owner.workflow_ref : task?.created_from_workflow_ref;
|
|
24
|
+
inherited =
|
|
25
|
+
(task === null ? null : readCommunicationLanguage(db, { kind: "task", ref: task.id })) ??
|
|
26
|
+
(workflowRef === undefined
|
|
27
|
+
? null
|
|
28
|
+
: readCommunicationLanguage(db, { kind: "workflow", ref: workflowRef }));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return createLanguageContext({
|
|
32
|
+
scope: "reply",
|
|
33
|
+
inherited,
|
|
34
|
+
messages: [
|
|
35
|
+
...messages.filter((message) => message.created_at < trigger.created_at ||
|
|
36
|
+
(message.created_at === trigger.created_at && message.id < trigger.id)),
|
|
37
|
+
trigger,
|
|
38
|
+
].map(messageLanguageEvidence),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=language-context.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SqliteDatabase } from "../store/index.js";
|
|
2
|
+
import { type LanguageDecision, type LanguagePreference } from "../communication-language/index.js";
|
|
3
|
+
export type LanguageOwner = {
|
|
4
|
+
kind: "project" | "scratchpad" | "workflow" | "task";
|
|
5
|
+
ref: string;
|
|
6
|
+
};
|
|
7
|
+
export type StoredCommunicationLanguage = LanguagePreference & {
|
|
8
|
+
basis: LanguageDecision["basis"];
|
|
9
|
+
activity_ref: string | null;
|
|
10
|
+
source_message_id: string | null;
|
|
11
|
+
source_created_at: string | null;
|
|
12
|
+
};
|
|
13
|
+
export declare function readCommunicationLanguage(db: SqliteDatabase, owner?: LanguageOwner): StoredCommunicationLanguage | null;
|
|
14
|
+
export declare function writeCommunicationLanguage(db: SqliteDatabase, input: {
|
|
15
|
+
owner: LanguageOwner;
|
|
16
|
+
language: LanguagePreference & {
|
|
17
|
+
basis: LanguageDecision["basis"];
|
|
18
|
+
};
|
|
19
|
+
activityRef?: string;
|
|
20
|
+
sourceCursor?: {
|
|
21
|
+
message_id: string;
|
|
22
|
+
created_at: string;
|
|
23
|
+
};
|
|
24
|
+
now: () => Date;
|
|
25
|
+
}): void;
|
|
26
|
+
export declare function copyCommunicationLanguage(db: SqliteDatabase, input: {
|
|
27
|
+
from: LanguageOwner;
|
|
28
|
+
to: LanguageOwner;
|
|
29
|
+
now: () => Date;
|
|
30
|
+
}): void;
|
|
31
|
+
//# sourceMappingURL=communication-language.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { COMMUNICATION_LANGUAGE_POLICY_VERSION, isLanguagePreference, } from "../communication-language/index.js";
|
|
2
|
+
export function readCommunicationLanguage(db, owner = { kind: "project", ref: "current" }) {
|
|
3
|
+
const row = db
|
|
4
|
+
.prepare(`SELECT locale, preference, basis, activity_ref, source_message_id, source_created_at
|
|
5
|
+
FROM communication_language_state WHERE owner_kind = ? AND owner_ref = ?`)
|
|
6
|
+
.get(owner.kind, owner.ref);
|
|
7
|
+
if (row === undefined)
|
|
8
|
+
return null;
|
|
9
|
+
if (!isLanguagePreference(row))
|
|
10
|
+
throw new Error("communication_language_state_invalid");
|
|
11
|
+
return row;
|
|
12
|
+
}
|
|
13
|
+
export function writeCommunicationLanguage(db, input) {
|
|
14
|
+
if (!isLanguagePreference(input.language))
|
|
15
|
+
throw new Error("communication_language_state_invalid");
|
|
16
|
+
db.prepare(`INSERT INTO communication_language_state
|
|
17
|
+
(owner_kind, owner_ref, locale, preference, basis, policy_version, activity_ref,
|
|
18
|
+
source_message_id, source_created_at, updated_at)
|
|
19
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
20
|
+
ON CONFLICT(owner_kind, owner_ref) DO UPDATE SET
|
|
21
|
+
locale = excluded.locale, preference = excluded.preference, basis = excluded.basis,
|
|
22
|
+
policy_version = excluded.policy_version, activity_ref = excluded.activity_ref,
|
|
23
|
+
source_message_id = excluded.source_message_id, source_created_at = excluded.source_created_at,
|
|
24
|
+
updated_at = excluded.updated_at
|
|
25
|
+
WHERE excluded.owner_kind != 'project' OR
|
|
26
|
+
communication_language_state.source_created_at IS NULL OR
|
|
27
|
+
(excluded.source_created_at, excluded.source_message_id) >
|
|
28
|
+
(communication_language_state.source_created_at, communication_language_state.source_message_id)`).run(input.owner.kind, input.owner.ref, input.language.locale, input.language.preference, input.language.basis, COMMUNICATION_LANGUAGE_POLICY_VERSION, input.activityRef ?? null, input.sourceCursor?.message_id ?? null, input.sourceCursor?.created_at ?? null, input.now().toISOString());
|
|
29
|
+
}
|
|
30
|
+
export function copyCommunicationLanguage(db, input) {
|
|
31
|
+
const language = readCommunicationLanguage(db, input.from);
|
|
32
|
+
if (language === null)
|
|
33
|
+
return;
|
|
34
|
+
writeCommunicationLanguage(db, { owner: input.to, language, now: input.now });
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=communication-language.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { withHostStoreTransaction } from "../store/index.js";
|
|
2
2
|
import { readScratchpadProjection } from "./scratchpad.js";
|
|
3
|
+
import { readCommunicationLanguage, copyCommunicationLanguage } from "./communication-language.js";
|
|
3
4
|
import { readScratchpadSourceState } from "./scratchpad-source-state.js";
|
|
4
5
|
import { nowIso, stringifyJson } from "./serialization.js";
|
|
5
6
|
function cursorIsAfter(left, right) {
|
|
@@ -206,6 +207,14 @@ export function captureTaskCompileContext(db, input) {
|
|
|
206
207
|
if (scratchpad.state !== "ready" || scratchpad.updated_at === undefined) {
|
|
207
208
|
throw new Error("A ready Scratchpad is required to capture Task Compile Context");
|
|
208
209
|
}
|
|
210
|
+
const language = readCommunicationLanguage(tx, { kind: "scratchpad", ref: "current" });
|
|
211
|
+
if (language !== null && language.activity_ref === scratchpad.updated_by_activity_ref) {
|
|
212
|
+
copyCommunicationLanguage(tx, {
|
|
213
|
+
from: { kind: "scratchpad", ref: "current" },
|
|
214
|
+
to: { kind: "workflow", ref: input.workflow_ref },
|
|
215
|
+
now: input.now ?? (() => new Date()),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
209
218
|
const sourceState = readScratchpadSourceState(tx);
|
|
210
219
|
const submission = readPendingTaskCompileSubmission(tx, input.workflow_ref);
|
|
211
220
|
const sources = readCurrentScratchpadSourceRefs(tx, submission?.source_window.through);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { MessageProjection } from "@tutti/shared/schemas/api";
|
|
2
|
+
export declare const COMMUNICATION_LANGUAGE_POLICY_VERSION = 1;
|
|
3
|
+
export type LanguagePreference = {
|
|
4
|
+
locale: string;
|
|
5
|
+
preference: "auto" | "fixed";
|
|
6
|
+
};
|
|
7
|
+
export type LanguageDecision = LanguagePreference & {
|
|
8
|
+
basis: "discussion" | "explicit" | "inherited" | "default";
|
|
9
|
+
evidence_index: number | null;
|
|
10
|
+
};
|
|
11
|
+
export type LanguageEvidenceMessage = {
|
|
12
|
+
role: "human" | "tutti" | "operator" | "system";
|
|
13
|
+
text: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function messageLanguageEvidence(message: MessageProjection): LanguageEvidenceMessage;
|
|
16
|
+
export type CommunicationLanguageContext = {
|
|
17
|
+
policy_version: 1;
|
|
18
|
+
scope: "scratchpad" | "reply" | "workflow" | "summary" | "documents";
|
|
19
|
+
inherited: LanguagePreference | null;
|
|
20
|
+
pinned: boolean;
|
|
21
|
+
evidence: Array<{
|
|
22
|
+
role: "human" | "operator";
|
|
23
|
+
text: string;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
26
|
+
export declare function isLanguagePreference(value: unknown): value is LanguagePreference;
|
|
27
|
+
export declare function isLanguageDecision(value: unknown): value is LanguageDecision;
|
|
28
|
+
/** A language sample, not task context. The original message is never rewritten. */
|
|
29
|
+
export declare function authoredLanguageSample(text: string): string;
|
|
30
|
+
export declare function createLanguageContext(input: {
|
|
31
|
+
scope: CommunicationLanguageContext["scope"];
|
|
32
|
+
inherited?: LanguagePreference | null;
|
|
33
|
+
pinned?: boolean;
|
|
34
|
+
messages?: readonly LanguageEvidenceMessage[];
|
|
35
|
+
}): CommunicationLanguageContext;
|
|
36
|
+
export declare function languageDecisionMatchesContext(decision: unknown, context: CommunicationLanguageContext): decision is LanguageDecision;
|
|
37
|
+
export declare function languageDiagnosticFields(context: CommunicationLanguageContext): {
|
|
38
|
+
language_policy_version: 1;
|
|
39
|
+
language_scope: "scratchpad" | "summary" | "workflow" | "reply" | "documents";
|
|
40
|
+
language_locale: string;
|
|
41
|
+
language_pinned: boolean;
|
|
42
|
+
language_evidence_count: number;
|
|
43
|
+
};
|
|
44
|
+
export type LanguageAwareOutput = {
|
|
45
|
+
communication_language?: LanguageDecision;
|
|
46
|
+
};
|
|
47
|
+
export declare const LANGUAGE_DECISION_SCHEMA: {
|
|
48
|
+
readonly type: "object";
|
|
49
|
+
readonly additionalProperties: false;
|
|
50
|
+
readonly required: readonly ["locale", "preference", "basis", "evidence_index"];
|
|
51
|
+
readonly properties: {
|
|
52
|
+
readonly locale: {
|
|
53
|
+
readonly type: "string";
|
|
54
|
+
readonly pattern: string;
|
|
55
|
+
};
|
|
56
|
+
readonly preference: {
|
|
57
|
+
readonly type: "string";
|
|
58
|
+
readonly enum: readonly ["auto", "fixed"];
|
|
59
|
+
};
|
|
60
|
+
readonly basis: {
|
|
61
|
+
readonly type: "string";
|
|
62
|
+
readonly enum: readonly ["discussion", "explicit", "inherited", "default"];
|
|
63
|
+
};
|
|
64
|
+
readonly evidence_index: {
|
|
65
|
+
readonly anyOf: readonly [{
|
|
66
|
+
readonly type: "integer";
|
|
67
|
+
readonly minimum: 0;
|
|
68
|
+
readonly maximum: 11;
|
|
69
|
+
}, {
|
|
70
|
+
readonly type: "null";
|
|
71
|
+
}];
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export const COMMUNICATION_LANGUAGE_POLICY_VERSION = 1;
|
|
2
|
+
export function messageLanguageEvidence(message) {
|
|
3
|
+
const feedback = message.refs?.artifact_feedback;
|
|
4
|
+
return {
|
|
5
|
+
role: message.author.kind === "human"
|
|
6
|
+
? "human"
|
|
7
|
+
: message.refs?.development_operator === true
|
|
8
|
+
? "operator"
|
|
9
|
+
: "system",
|
|
10
|
+
text: message.refs?.reference_file !== undefined
|
|
11
|
+
? ""
|
|
12
|
+
: feedback === undefined
|
|
13
|
+
? message.body
|
|
14
|
+
: [feedback.summary, ...feedback.annotations.map((entry) => entry.comment)]
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.join("\n"),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const BASES = new Set(["discussion", "explicit", "inherited", "default"]);
|
|
20
|
+
const LOCALE_PATTERN = /^[a-z]{2,3}(?:-[A-Z][a-z]{3})?(?:-[A-Z]{2}|-\d{3})?$/u;
|
|
21
|
+
export function isLanguagePreference(value) {
|
|
22
|
+
if (typeof value !== "object" || value === null)
|
|
23
|
+
return false;
|
|
24
|
+
const entry = value;
|
|
25
|
+
if (entry.preference !== "auto" && entry.preference !== "fixed")
|
|
26
|
+
return false;
|
|
27
|
+
if (typeof entry.locale !== "string" || !LOCALE_PATTERN.test(entry.locale))
|
|
28
|
+
return false;
|
|
29
|
+
try {
|
|
30
|
+
return Intl.getCanonicalLocales(entry.locale)[0] === entry.locale && entry.locale !== "und";
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function isLanguageDecision(value) {
|
|
37
|
+
if (!isLanguagePreference(value))
|
|
38
|
+
return false;
|
|
39
|
+
const entry = value;
|
|
40
|
+
return (BASES.has(entry.basis ?? "") &&
|
|
41
|
+
(entry.evidence_index === null ||
|
|
42
|
+
(Number.isSafeInteger(entry.evidence_index) && (entry.evidence_index ?? -1) >= 0)) &&
|
|
43
|
+
Object.keys(value).every((key) => ["locale", "preference", "basis", "evidence_index"].includes(key)));
|
|
44
|
+
}
|
|
45
|
+
/** A language sample, not task context. The original message is never rewritten. */
|
|
46
|
+
export function authoredLanguageSample(text) {
|
|
47
|
+
const sample = text
|
|
48
|
+
.replace(/```[^]*?```|~~~[^]*?~~~/gu, " ")
|
|
49
|
+
.replace(/^(?: {0,3}>| {4}|\t).*$/gmu, " ")
|
|
50
|
+
.replace(/`[^`]*`/gu, " ")
|
|
51
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/gu, " ")
|
|
52
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/gu, "$1")
|
|
53
|
+
.replace(/https?:\/\/\S+|@tutti\b/giu, " ")
|
|
54
|
+
.replace(/\s+/gu, " ")
|
|
55
|
+
.trim();
|
|
56
|
+
return sample.length <= 800 ? sample : `${sample.slice(0, 398)} … ${sample.slice(-399)}`;
|
|
57
|
+
}
|
|
58
|
+
export function createLanguageContext(input) {
|
|
59
|
+
const evidence = (input.messages ?? []).flatMap((message) => {
|
|
60
|
+
if (message.role !== "human" && message.role !== "operator")
|
|
61
|
+
return [];
|
|
62
|
+
const text = authoredLanguageSample(message.text);
|
|
63
|
+
return /\p{L}/u.test(text) ? [{ role: message.role, text }] : [];
|
|
64
|
+
});
|
|
65
|
+
// Prefer humans. Operator-only tests can infer locally but cannot update project memory.
|
|
66
|
+
const selected = input.scope !== "reply" && evidence.some((entry) => entry.role === "human")
|
|
67
|
+
? evidence.filter((entry) => entry.role === "human")
|
|
68
|
+
: evidence;
|
|
69
|
+
return {
|
|
70
|
+
policy_version: COMMUNICATION_LANGUAGE_POLICY_VERSION,
|
|
71
|
+
scope: input.scope,
|
|
72
|
+
inherited: input.inherited == null
|
|
73
|
+
? null
|
|
74
|
+
: { locale: input.inherited.locale, preference: input.inherited.preference },
|
|
75
|
+
pinned: input.pinned ?? false,
|
|
76
|
+
evidence: selected.slice(-12),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export function languageDecisionMatchesContext(decision, context) {
|
|
80
|
+
if (!isLanguageDecision(decision))
|
|
81
|
+
return false;
|
|
82
|
+
if (decision.basis === "inherited") {
|
|
83
|
+
return (decision.evidence_index === null &&
|
|
84
|
+
decision.locale === context.inherited?.locale &&
|
|
85
|
+
decision.preference === context.inherited.preference);
|
|
86
|
+
}
|
|
87
|
+
if (decision.basis === "default") {
|
|
88
|
+
return (decision.evidence_index === null &&
|
|
89
|
+
context.inherited === null &&
|
|
90
|
+
decision.locale === "en" &&
|
|
91
|
+
decision.preference === "auto");
|
|
92
|
+
}
|
|
93
|
+
if (decision.evidence_index === null || !context.evidence[decision.evidence_index])
|
|
94
|
+
return false;
|
|
95
|
+
if (context.scope === "summary" || context.scope === "documents")
|
|
96
|
+
return false;
|
|
97
|
+
if (decision.basis === "discussion") {
|
|
98
|
+
return (!context.pinned && context.inherited?.preference !== "fixed" && decision.preference === "auto");
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
export function languageDiagnosticFields(context) {
|
|
103
|
+
return {
|
|
104
|
+
language_policy_version: context.policy_version,
|
|
105
|
+
language_scope: context.scope,
|
|
106
|
+
language_locale: context.inherited?.locale ?? "undetermined",
|
|
107
|
+
language_pinned: context.pinned,
|
|
108
|
+
language_evidence_count: context.evidence.length,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export const LANGUAGE_DECISION_SCHEMA = {
|
|
112
|
+
type: "object",
|
|
113
|
+
additionalProperties: false,
|
|
114
|
+
required: ["locale", "preference", "basis", "evidence_index"],
|
|
115
|
+
properties: {
|
|
116
|
+
locale: { type: "string", pattern: LOCALE_PATTERN.source },
|
|
117
|
+
preference: { type: "string", enum: ["auto", "fixed"] },
|
|
118
|
+
basis: { type: "string", enum: ["discussion", "explicit", "inherited", "default"] },
|
|
119
|
+
evidence_index: { anyOf: [{ type: "integer", minimum: 0, maximum: 11 }, { type: "null" }] },
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
//# sourceMappingURL=index.js.map
|