@xfey/tutti 0.1.125 → 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/web/assets/{homepage-motion-scene-Ctt45F9o.js → homepage-motion-scene-DyEmWwvt.js} +1 -1
- package/web/assets/{index-BJpTcX_E.js → index-By3idT4U.js} +2 -2
- package/web/assets/{index-DTuz8Hb5.css → index-DSBjOZmJ.css} +1 -1
- package/web/index.html +2 -2
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { parseArgs, parseEnv } from "node:util";
|
|
6
|
+
import { createActivityRef, createProjectId, createTaskId, createTaskModuleId, createWorkflowInvocationRef, } from "@tutti/shared/ids";
|
|
7
|
+
import { createOpenAiProcedureWorkflowRunner } from "../control-plane/workflows/openai.js";
|
|
8
|
+
import { createOpenAiStructuredOutputClient, OpenAiStructuredOutputError, } from "../providers/openai/sdk-procedure-runner.js";
|
|
9
|
+
import { runWorkspaceWriteTask } from "../run-pipeline/task-run-invocation.js";
|
|
10
|
+
import { CodexAppServerWorkspaceWriteRunError } from "../providers/openai/app-server/workspace-write-run.js";
|
|
11
|
+
import { CodexAppServerReadOnlyProcedureError } from "../providers/openai/app-server/read-only-procedure.js";
|
|
12
|
+
import { createLanguageContext } from "./index.js";
|
|
13
|
+
import { isLanguageDecision, languageDecisionMatchesContext, } from "./index.js";
|
|
14
|
+
import { isScratchpadRefreshStructuredOutput } from "../control-plane/workflows/structured-output.js";
|
|
15
|
+
// Opt-in paid synthetic evaluation, never called by Host or normal unit tests.
|
|
16
|
+
let activeCase = "configuration";
|
|
17
|
+
let expectedLanguage;
|
|
18
|
+
let lastProgress = "";
|
|
19
|
+
const smokeStartedAt = Date.now();
|
|
20
|
+
function reportProgress(stage, action) {
|
|
21
|
+
const key = `${activeCase}:${stage}:${action ?? ""}`;
|
|
22
|
+
if (key === lastProgress)
|
|
23
|
+
return;
|
|
24
|
+
lastProgress = key;
|
|
25
|
+
console.log(JSON.stringify({
|
|
26
|
+
case: activeCase,
|
|
27
|
+
stage,
|
|
28
|
+
...(action === undefined ? {} : { action }),
|
|
29
|
+
elapsed_ms: Date.now() - smokeStartedAt,
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
function report(status) {
|
|
33
|
+
console.log(JSON.stringify({ case: activeCase, status }));
|
|
34
|
+
}
|
|
35
|
+
class SmokeAssertionError extends Error {
|
|
36
|
+
code;
|
|
37
|
+
constructor(code) {
|
|
38
|
+
super(code);
|
|
39
|
+
this.code = code;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function requireCondition(condition, code = "fixture_assertion_failed") {
|
|
43
|
+
if (!condition)
|
|
44
|
+
throw new SmokeAssertionError(code);
|
|
45
|
+
}
|
|
46
|
+
function proseMatches(text, locale) {
|
|
47
|
+
// A narrow fixture assertion, NOT a production language detector or translation gate.
|
|
48
|
+
return locale.startsWith("zh") ? /\p{Script=Han}/u.test(text) : !/\p{Script=Han}/u.test(text);
|
|
49
|
+
}
|
|
50
|
+
async function main() {
|
|
51
|
+
const { values } = parseArgs({
|
|
52
|
+
options: {
|
|
53
|
+
"env-file": { type: "string" },
|
|
54
|
+
model: { type: "string" },
|
|
55
|
+
"from-case": { type: "string" },
|
|
56
|
+
"turn-timeout-ms": { type: "string" },
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
if (!values["env-file"] || !values.model)
|
|
60
|
+
throw new Error("explicit_smoke_configuration_required");
|
|
61
|
+
const turnTimeoutMs = Number(values["turn-timeout-ms"] ?? 180_000);
|
|
62
|
+
requireCondition(Number.isSafeInteger(turnTimeoutMs) && turnTimeoutMs >= 30_000 && turnTimeoutMs <= 1_200_000);
|
|
63
|
+
// Do not export credentials into the process or child environment.
|
|
64
|
+
const env = parseEnv(readFileSync(values["env-file"], "utf8"));
|
|
65
|
+
const apiKey = env.TUTTI_API_KEY;
|
|
66
|
+
if (!apiKey)
|
|
67
|
+
throw new Error("missing_smoke_credential");
|
|
68
|
+
const config = {
|
|
69
|
+
default_model: values.model,
|
|
70
|
+
...(env.TUTTI_BASE_URL ? { api_base_url: env.TUTTI_BASE_URL } : {}),
|
|
71
|
+
};
|
|
72
|
+
const root = mkdtempSync(join(tmpdir(), "tutti-language-smoke-"));
|
|
73
|
+
const workspaceRoot = join(root, "repo");
|
|
74
|
+
const privateHome = join(root, "home");
|
|
75
|
+
const tuttiHome = join(root, "tutti");
|
|
76
|
+
for (const path of [workspaceRoot, privateHome, tuttiHome])
|
|
77
|
+
mkdirSync(path, { mode: 0o700 });
|
|
78
|
+
let fixtureCleanupSafe = true;
|
|
79
|
+
const runtime = {
|
|
80
|
+
apiKey,
|
|
81
|
+
workspaceRoot,
|
|
82
|
+
tuttiHome,
|
|
83
|
+
projectId: createProjectId(),
|
|
84
|
+
baseEnv: { HOME: privateHome, PATH: process.env.PATH, LANG: "en_US.UTF-8" },
|
|
85
|
+
requestTimeoutMs: 60_000,
|
|
86
|
+
turnTimeoutMs,
|
|
87
|
+
onLifecycleIncident: (incident) => {
|
|
88
|
+
if (incident.code === "process_termination_failed")
|
|
89
|
+
fixtureCleanupSafe = false;
|
|
90
|
+
console.log(JSON.stringify({ case: activeCase, diagnostic: "lifecycle", reason_code: incident.code }));
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
try {
|
|
94
|
+
writeFileSync(join(workspaceRoot, "README.md"), "# English repository\n\nA minimal synthetic greeting fixture. Keep this README in English.\n", { mode: 0o600 });
|
|
95
|
+
for (const args of [
|
|
96
|
+
["init", "-b", "main"],
|
|
97
|
+
["config", "user.name", "Tutti Fixture"],
|
|
98
|
+
["config", "user.email", "fixture@example.invalid"],
|
|
99
|
+
["add", "."],
|
|
100
|
+
["commit", "-m", "fixture"],
|
|
101
|
+
])
|
|
102
|
+
execFileSync("git", args, { cwd: workspaceRoot, stdio: "ignore" });
|
|
103
|
+
const client = createOpenAiStructuredOutputClient({
|
|
104
|
+
apiKey,
|
|
105
|
+
...(config.api_base_url ? { apiBaseUrl: config.api_base_url } : {}),
|
|
106
|
+
});
|
|
107
|
+
const runner = createOpenAiProcedureWorkflowRunner({
|
|
108
|
+
config,
|
|
109
|
+
client: {
|
|
110
|
+
responses: {
|
|
111
|
+
create: async (body, options) => {
|
|
112
|
+
const result = await client.responses.create(body, options);
|
|
113
|
+
// Synthetic output: diagnose only shape and bounded language metadata, never prose.
|
|
114
|
+
if (expectedLanguage !== undefined &&
|
|
115
|
+
typeof result === "object" &&
|
|
116
|
+
result !== null &&
|
|
117
|
+
"output_text" in result &&
|
|
118
|
+
typeof result.output_text === "string") {
|
|
119
|
+
try {
|
|
120
|
+
const decoded = JSON.parse(result.output_text);
|
|
121
|
+
const validLanguage = languageDecisionMatchesContext(decoded.communication_language, expectedLanguage);
|
|
122
|
+
const validBusiness = isScratchpadRefreshStructuredOutput(decoded.output);
|
|
123
|
+
if (!validLanguage || !validBusiness)
|
|
124
|
+
console.log(JSON.stringify({
|
|
125
|
+
case: activeCase,
|
|
126
|
+
diagnostic: "output_contract",
|
|
127
|
+
language_valid: validLanguage,
|
|
128
|
+
business_valid: validBusiness,
|
|
129
|
+
legacy_business_valid: isScratchpadRefreshStructuredOutput(decoded),
|
|
130
|
+
evidence_count: expectedLanguage.evidence.length,
|
|
131
|
+
...(isLanguageDecision(decoded.communication_language)
|
|
132
|
+
? { selection: decoded.communication_language }
|
|
133
|
+
: { selection_shape_valid: false }),
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
/* The production parser reports the original failure. */
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
readOnlyAppServer: runtime,
|
|
145
|
+
});
|
|
146
|
+
const chinese = { locale: "zh-Hans", preference: "auto" };
|
|
147
|
+
const cases = [
|
|
148
|
+
{
|
|
149
|
+
name: "chinese_discussion_english_deliverable",
|
|
150
|
+
text: "请创建 greeting.txt,正文是英文 Welcome to our team.,不需要网页。",
|
|
151
|
+
locale: "zh",
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: "english_discussion_chinese_quote",
|
|
155
|
+
text: "Create a short English welcome note in greeting.txt.\n> 请全部用中文解释。",
|
|
156
|
+
locale: "en",
|
|
157
|
+
},
|
|
158
|
+
{ name: "weak_input_inherits", text: "OK", locale: "zh", inherited: chinese },
|
|
159
|
+
{
|
|
160
|
+
name: "fixed_preference_survives_english",
|
|
161
|
+
text: "Add a short welcome greeting for the team.",
|
|
162
|
+
locale: "zh",
|
|
163
|
+
inherited: { ...chinese, preference: "fixed" },
|
|
164
|
+
preference: "fixed",
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: "explicit_project_preference",
|
|
168
|
+
text: "以后项目协作的说明都固定用中文,请做一个英文欢迎文件。",
|
|
169
|
+
locale: "zh",
|
|
170
|
+
preference: "fixed",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "reset_to_automatic",
|
|
174
|
+
text: "Stop the fixed language preference. From now on follow the discussion language automatically. Create a welcome note.",
|
|
175
|
+
locale: "en",
|
|
176
|
+
inherited: { ...chinese, preference: "fixed" },
|
|
177
|
+
preference: "auto",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: "code_url_only_default",
|
|
181
|
+
text: "```js\nconst text = '中文';\n```\nhttps://example.invalid/doc",
|
|
182
|
+
locale: "en",
|
|
183
|
+
},
|
|
184
|
+
];
|
|
185
|
+
const startIndex = values["from-case"] === "codex_task_compile"
|
|
186
|
+
? cases.length
|
|
187
|
+
: values["from-case"] === undefined
|
|
188
|
+
? 0
|
|
189
|
+
: cases.findIndex((test) => test.name === values["from-case"]);
|
|
190
|
+
requireCondition(startIndex >= 0);
|
|
191
|
+
// Tail reruns use a synthetic fixed seed; no prior evaluation output is persisted.
|
|
192
|
+
let seed = {
|
|
193
|
+
messages: [{ role: "human", text: cases[0].text }],
|
|
194
|
+
worklist: { modules: [] },
|
|
195
|
+
};
|
|
196
|
+
let snapshot = {
|
|
197
|
+
topic: "英文欢迎文件",
|
|
198
|
+
background_summary: "为团队创建英文欢迎文件。",
|
|
199
|
+
current_consensus: ["文件正文为英文。"],
|
|
200
|
+
open_questions: [],
|
|
201
|
+
task_changes: ["创建 greeting.txt,内容为 Welcome to our team."],
|
|
202
|
+
};
|
|
203
|
+
for (const test of cases.slice(startIndex)) {
|
|
204
|
+
activeCase = test.name;
|
|
205
|
+
report("started");
|
|
206
|
+
const messages = [
|
|
207
|
+
{
|
|
208
|
+
role: "tutti",
|
|
209
|
+
text: "Previous English analysis of this repository and possible implementation.",
|
|
210
|
+
},
|
|
211
|
+
{ role: "human", text: test.text },
|
|
212
|
+
];
|
|
213
|
+
const input = {
|
|
214
|
+
messages,
|
|
215
|
+
worklist: { modules: [] },
|
|
216
|
+
communication_language: createLanguageContext({
|
|
217
|
+
scope: "scratchpad",
|
|
218
|
+
...(test.inherited ? { inherited: test.inherited } : {}),
|
|
219
|
+
messages,
|
|
220
|
+
}),
|
|
221
|
+
};
|
|
222
|
+
expectedLanguage = input.communication_language;
|
|
223
|
+
const output = await runner.runScratchpadRefresh(input, {
|
|
224
|
+
workflow_ref: createWorkflowInvocationRef(),
|
|
225
|
+
activity_ref: createActivityRef(),
|
|
226
|
+
});
|
|
227
|
+
const language = output.communication_language;
|
|
228
|
+
const prose = output.decision === "ready"
|
|
229
|
+
? [output.scratchpad_snapshot.topic, output.scratchpad_snapshot.background_summary].join(" ")
|
|
230
|
+
: output.request_payload.request;
|
|
231
|
+
const selectionMatches = language !== undefined && language.locale.startsWith(test.locale);
|
|
232
|
+
const preferenceMatches = test.preference === undefined || language?.preference === test.preference;
|
|
233
|
+
const proseLanguageMatches = proseMatches(prose, test.locale);
|
|
234
|
+
if (!selectionMatches || !preferenceMatches || !proseLanguageMatches) {
|
|
235
|
+
console.log(JSON.stringify({
|
|
236
|
+
case: activeCase,
|
|
237
|
+
diagnostic: "language_evaluation",
|
|
238
|
+
expected_locale_prefix: test.locale,
|
|
239
|
+
expected_preference: test.preference,
|
|
240
|
+
actual_locale: language?.locale,
|
|
241
|
+
actual_preference: language?.preference,
|
|
242
|
+
actual_basis: language?.basis,
|
|
243
|
+
selection_matches: selectionMatches,
|
|
244
|
+
preference_matches: preferenceMatches,
|
|
245
|
+
prose_language_matches: proseLanguageMatches,
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
requireCondition(selectionMatches, "language_selection_mismatch");
|
|
249
|
+
requireCondition(preferenceMatches, "language_preference_mismatch");
|
|
250
|
+
requireCondition(proseLanguageMatches, "language_prose_mismatch");
|
|
251
|
+
if (test === cases[0] && output.decision === "ready") {
|
|
252
|
+
seed = input;
|
|
253
|
+
snapshot = output.scratchpad_snapshot;
|
|
254
|
+
}
|
|
255
|
+
report("passed");
|
|
256
|
+
}
|
|
257
|
+
activeCase = "codex_task_compile";
|
|
258
|
+
report("started");
|
|
259
|
+
const workflowRef = createWorkflowInvocationRef();
|
|
260
|
+
const language = createLanguageContext({ scope: "workflow", inherited: chinese, pinned: true });
|
|
261
|
+
const compiled = await runner.runTaskCompile({
|
|
262
|
+
workflow_ref: workflowRef,
|
|
263
|
+
communication_language: language,
|
|
264
|
+
scratchpad: snapshot,
|
|
265
|
+
source_messages: seed.messages,
|
|
266
|
+
worklist: { modules: [] },
|
|
267
|
+
}, {
|
|
268
|
+
workflow_ref: workflowRef,
|
|
269
|
+
activity_ref: createActivityRef(),
|
|
270
|
+
reportProgress: (progress) => reportProgress(progress.phase ?? "task_compile", progress.runtime?.current_action),
|
|
271
|
+
});
|
|
272
|
+
requireCondition(compiled.decision === "ready" && compiled.proposal.tasks.length === 1);
|
|
273
|
+
requireCondition(compiled.communication_language?.locale === "zh-Hans");
|
|
274
|
+
const proposed = compiled.proposal.tasks[0];
|
|
275
|
+
requireCondition(proseMatches(proposed.title + proposed.contract.goal, "zh"));
|
|
276
|
+
report("passed");
|
|
277
|
+
activeCase = "codex_execute_finalize_english_file";
|
|
278
|
+
report("started");
|
|
279
|
+
const taskId = createTaskId();
|
|
280
|
+
const moduleId = createTaskModuleId();
|
|
281
|
+
const activityRef = createActivityRef();
|
|
282
|
+
const now = new Date().toISOString();
|
|
283
|
+
const task = {
|
|
284
|
+
id: taskId,
|
|
285
|
+
module_id: moduleId,
|
|
286
|
+
title: proposed.title,
|
|
287
|
+
summary: proposed.summary,
|
|
288
|
+
status: "running",
|
|
289
|
+
order: 1,
|
|
290
|
+
current_activity_ref: activityRef,
|
|
291
|
+
created_from_workflow_ref: workflowRef,
|
|
292
|
+
created_at: now,
|
|
293
|
+
updated_at: now,
|
|
294
|
+
};
|
|
295
|
+
const result = await runWorkspaceWriteTask({ ...runtime, config }, {
|
|
296
|
+
communication_language: language,
|
|
297
|
+
on_stage: (progress) => reportProgress(progress.stage, progress.runtime?.current_action),
|
|
298
|
+
activity_ref: activityRef,
|
|
299
|
+
task,
|
|
300
|
+
task_detail: {
|
|
301
|
+
task,
|
|
302
|
+
detail: { task_id: taskId, contract: proposed.contract, result: {}, updated_at: now },
|
|
303
|
+
},
|
|
304
|
+
source_messages: seed.messages,
|
|
305
|
+
run_lineage: { kind: "initial", attempt: 1 },
|
|
306
|
+
dispatch_context: {
|
|
307
|
+
trigger: "manual_run_now",
|
|
308
|
+
queue_scope: { kind: "worklist" },
|
|
309
|
+
current_task_id: taskId,
|
|
310
|
+
ordered_task_refs: [taskId],
|
|
311
|
+
current_module_id: moduleId,
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
requireCondition(result.decision === "completed", "execute_result_unexpected");
|
|
315
|
+
requireCondition(proseMatches(result.summary, "zh"), "execute_language_mismatch");
|
|
316
|
+
requireCondition(readFileSync(join(workspaceRoot, "greeting.txt"), "utf8").trim() === "Welcome to our team.", "deliverable_mismatch");
|
|
317
|
+
const readme = readFileSync(join(workspaceRoot, "README.md"), "utf8");
|
|
318
|
+
requireCondition(readme.startsWith("# English repository") && proseMatches(readme, "en"), "existing_document_changed");
|
|
319
|
+
report("passed");
|
|
320
|
+
console.log(JSON.stringify({
|
|
321
|
+
status: "passed",
|
|
322
|
+
cases: cases.length - startIndex + 2,
|
|
323
|
+
model: config.default_model,
|
|
324
|
+
turn_timeout_ms: turnTimeoutMs,
|
|
325
|
+
...(values["from-case"] === undefined ? {} : { from_case: values["from-case"] }),
|
|
326
|
+
}));
|
|
327
|
+
}
|
|
328
|
+
finally {
|
|
329
|
+
if (fixtureCleanupSafe)
|
|
330
|
+
rmSync(root, { recursive: true, force: true });
|
|
331
|
+
else
|
|
332
|
+
console.log(JSON.stringify({
|
|
333
|
+
case: activeCase,
|
|
334
|
+
diagnostic: "fixture_retained",
|
|
335
|
+
reason_code: "process_termination_unconfirmed",
|
|
336
|
+
}));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
void main().catch((error) => {
|
|
340
|
+
console.error(JSON.stringify({
|
|
341
|
+
case: activeCase,
|
|
342
|
+
status: "failed",
|
|
343
|
+
reason_code: error instanceof OpenAiStructuredOutputError
|
|
344
|
+
? error.reason
|
|
345
|
+
: error instanceof CodexAppServerWorkspaceWriteRunError ||
|
|
346
|
+
error instanceof CodexAppServerReadOnlyProcedureError ||
|
|
347
|
+
error instanceof SmokeAssertionError
|
|
348
|
+
? error.code
|
|
349
|
+
: "language_smoke_failed",
|
|
350
|
+
...(error instanceof OpenAiStructuredOutputError
|
|
351
|
+
? { invalid_response_kind: error.invalidResponseKind }
|
|
352
|
+
: {}),
|
|
353
|
+
...(error instanceof CodexAppServerWorkspaceWriteRunError
|
|
354
|
+
? {
|
|
355
|
+
turn_start_state: error.details.turn_start_state,
|
|
356
|
+
turn_timeout_ms: error.details.turn_timeout_ms,
|
|
357
|
+
http_status: error.details.turn_error_http_status,
|
|
358
|
+
notification_count: error.details.notification_count,
|
|
359
|
+
server_request_count: error.details.server_request_methods?.length,
|
|
360
|
+
recent_methods_include_turn_started: error.details.notification_methods?.includes("turn/started"),
|
|
361
|
+
recent_methods_include_item_completed: error.details.notification_methods?.includes("item/completed"),
|
|
362
|
+
elapsed_ms: Date.now() - smokeStartedAt,
|
|
363
|
+
}
|
|
364
|
+
: {}),
|
|
365
|
+
}));
|
|
366
|
+
process.exitCode = 1;
|
|
367
|
+
});
|
|
368
|
+
//# sourceMappingURL=live-smoke-cli.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type RenderPromptTemplateOptions } from "../prompt-templates/index.js";
|
|
2
|
+
import { type CommunicationLanguageContext, type LanguageDecision } from "./index.js";
|
|
3
|
+
export declare function renderLanguageAwarePrompt(options: RenderPromptTemplateOptions, language: CommunicationLanguageContext | undefined, reportLanguage?: boolean): string;
|
|
4
|
+
type ObjectSchema = {
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
};
|
|
7
|
+
export declare function languageOutputContract<T>(schema: ObjectSchema, validate: (value: unknown) => value is T, context: CommunicationLanguageContext | undefined): {
|
|
8
|
+
schema: ObjectSchema;
|
|
9
|
+
validate: (value: unknown) => value is T | {
|
|
10
|
+
communication_language: LanguageDecision;
|
|
11
|
+
output: T;
|
|
12
|
+
};
|
|
13
|
+
unwrap: (value: T | {
|
|
14
|
+
communication_language: LanguageDecision;
|
|
15
|
+
output: T;
|
|
16
|
+
}) => {
|
|
17
|
+
output: T;
|
|
18
|
+
language?: LanguageDecision;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { renderPromptTemplate, } from "../prompt-templates/index.js";
|
|
2
|
+
import { LANGUAGE_DECISION_SCHEMA, languageDecisionMatchesContext, } from "./index.js";
|
|
3
|
+
export function renderLanguageAwarePrompt(options, language, reportLanguage = false) {
|
|
4
|
+
const prompt = renderPromptTemplate(options);
|
|
5
|
+
if (language === undefined)
|
|
6
|
+
return prompt;
|
|
7
|
+
const policy = renderPromptTemplate({
|
|
8
|
+
templateKey: "communication_language",
|
|
9
|
+
...(options.promptsRoot === undefined ? {} : { promptsRoot: options.promptsRoot }),
|
|
10
|
+
variables: {
|
|
11
|
+
language_context_json: {
|
|
12
|
+
...language,
|
|
13
|
+
evidence: language.evidence.map((entry, evidence_index) => ({ evidence_index, ...entry })),
|
|
14
|
+
},
|
|
15
|
+
language_output_contract: reportLanguage
|
|
16
|
+
? renderPromptTemplate({
|
|
17
|
+
templateKey: "communication_language_result",
|
|
18
|
+
...(options.promptsRoot === undefined ? {} : { promptsRoot: options.promptsRoot }),
|
|
19
|
+
})
|
|
20
|
+
: "",
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
return `${policy}\n\n${prompt}`;
|
|
24
|
+
}
|
|
25
|
+
export function languageOutputContract(schema, validate, context) {
|
|
26
|
+
return {
|
|
27
|
+
schema: context === undefined
|
|
28
|
+
? schema
|
|
29
|
+
: {
|
|
30
|
+
type: "object",
|
|
31
|
+
additionalProperties: false,
|
|
32
|
+
required: ["communication_language", "output"],
|
|
33
|
+
properties: {
|
|
34
|
+
communication_language: {
|
|
35
|
+
...LANGUAGE_DECISION_SCHEMA,
|
|
36
|
+
properties: {
|
|
37
|
+
...LANGUAGE_DECISION_SCHEMA.properties,
|
|
38
|
+
evidence_index: context.evidence.length === 0
|
|
39
|
+
? { type: "null" }
|
|
40
|
+
: {
|
|
41
|
+
anyOf: [
|
|
42
|
+
{
|
|
43
|
+
type: "integer",
|
|
44
|
+
enum: context.evidence.map((_entry, index) => index),
|
|
45
|
+
},
|
|
46
|
+
{ type: "null" },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
output: schema,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
validate: (value) => {
|
|
55
|
+
if (context === undefined)
|
|
56
|
+
return validate(value);
|
|
57
|
+
if (typeof value !== "object" || value === null)
|
|
58
|
+
return false;
|
|
59
|
+
const candidate = value;
|
|
60
|
+
return (Object.keys(candidate).length === 2 &&
|
|
61
|
+
languageDecisionMatchesContext(candidate.communication_language, context) &&
|
|
62
|
+
validate(candidate.output));
|
|
63
|
+
},
|
|
64
|
+
unwrap: (value) => context === undefined
|
|
65
|
+
? { output: value }
|
|
66
|
+
: {
|
|
67
|
+
output: value.output,
|
|
68
|
+
language: value
|
|
69
|
+
.communication_language,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { MessageProjection } from "@tutti/shared/schemas/api";
|
|
2
|
+
import type { SqliteDatabase } from "../store/index.js";
|
|
3
|
+
import { type CommunicationLanguageContext, type LanguageDecision, type LanguageEvidenceMessage } from "../communication-language/index.js";
|
|
4
|
+
import { type LanguageOwner } from "../collaboration-state/communication-language.js";
|
|
5
|
+
export { messageLanguageEvidence } from "../communication-language/index.js";
|
|
6
|
+
export declare function sourceLanguageEvidence(db: SqliteDatabase, sources: readonly {
|
|
7
|
+
message_id: string;
|
|
8
|
+
}[]): LanguageEvidenceMessage[];
|
|
9
|
+
export declare function projectLanguageContext(db: SqliteDatabase, scope: CommunicationLanguageContext["scope"], messages?: readonly LanguageEvidenceMessage[]): CommunicationLanguageContext;
|
|
10
|
+
export declare function workflowLanguageContext(db: SqliteDatabase, input: {
|
|
11
|
+
workflowRef: string;
|
|
12
|
+
taskId?: string;
|
|
13
|
+
sources: readonly LanguageEvidenceMessage[];
|
|
14
|
+
answers?: readonly LanguageEvidenceMessage[];
|
|
15
|
+
}): CommunicationLanguageContext;
|
|
16
|
+
export declare function recordWorkflowLanguage(db: SqliteDatabase, input: {
|
|
17
|
+
owner: LanguageOwner;
|
|
18
|
+
language?: LanguageDecision;
|
|
19
|
+
activityRef: string;
|
|
20
|
+
now: () => Date;
|
|
21
|
+
}): void;
|
|
22
|
+
export declare function recordScratchpadLanguage(db: SqliteDatabase, input: {
|
|
23
|
+
language?: LanguageDecision;
|
|
24
|
+
context?: CommunicationLanguageContext;
|
|
25
|
+
sourceMessages: readonly MessageProjection[];
|
|
26
|
+
activityRef: string;
|
|
27
|
+
snapshot: boolean;
|
|
28
|
+
now: () => Date;
|
|
29
|
+
}): void;
|
|
30
|
+
export declare function logLanguageContext(logger: {
|
|
31
|
+
info: (fields: Record<string, unknown>, message: string) => void;
|
|
32
|
+
} | undefined, context: CommunicationLanguageContext, anchors: Record<string, string>, decision?: LanguageDecision): void;
|
|
33
|
+
//# sourceMappingURL=communication-language.d.ts.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readMessageProjectionById } from "../collaboration-state/index.js";
|
|
2
|
+
import { createLanguageContext, languageDecisionMatchesContext, languageDiagnosticFields, messageLanguageEvidence, } from "../communication-language/index.js";
|
|
3
|
+
import { readCommunicationLanguage, writeCommunicationLanguage, } from "../collaboration-state/communication-language.js";
|
|
4
|
+
export { messageLanguageEvidence } from "../communication-language/index.js";
|
|
5
|
+
export function sourceLanguageEvidence(db, sources) {
|
|
6
|
+
return sources.flatMap((source) => {
|
|
7
|
+
const message = readMessageProjectionById(db, source.message_id);
|
|
8
|
+
return message === null ? [] : [messageLanguageEvidence(message)];
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export function projectLanguageContext(db, scope, messages = []) {
|
|
12
|
+
return createLanguageContext({ scope, inherited: readCommunicationLanguage(db), messages });
|
|
13
|
+
}
|
|
14
|
+
export function workflowLanguageContext(db, input) {
|
|
15
|
+
const inherited = (input.taskId === undefined
|
|
16
|
+
? null
|
|
17
|
+
: readCommunicationLanguage(db, { kind: "task", ref: input.taskId })) ??
|
|
18
|
+
readCommunicationLanguage(db, { kind: "workflow", ref: input.workflowRef });
|
|
19
|
+
return createLanguageContext({
|
|
20
|
+
scope: "workflow",
|
|
21
|
+
inherited,
|
|
22
|
+
pinned: inherited !== null,
|
|
23
|
+
messages: inherited === null ? [...input.sources, ...(input.answers ?? [])] : (input.answers ?? []),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function recordWorkflowLanguage(db, input) {
|
|
27
|
+
if (input.language === undefined)
|
|
28
|
+
return;
|
|
29
|
+
writeCommunicationLanguage(db, { ...input, language: input.language });
|
|
30
|
+
}
|
|
31
|
+
export function recordScratchpadLanguage(db, input) {
|
|
32
|
+
if (input.language === undefined || input.context === undefined)
|
|
33
|
+
return;
|
|
34
|
+
if (!languageDecisionMatchesContext(input.language, input.context)) {
|
|
35
|
+
throw new Error("communication_language_decision_invalid");
|
|
36
|
+
}
|
|
37
|
+
if (input.snapshot)
|
|
38
|
+
recordWorkflowLanguage(db, {
|
|
39
|
+
owner: { kind: "scratchpad", ref: "current" },
|
|
40
|
+
...input,
|
|
41
|
+
});
|
|
42
|
+
const decision = input.language;
|
|
43
|
+
const evidence = decision.evidence_index === null ? undefined : input.context.evidence[decision.evidence_index];
|
|
44
|
+
const latestHuman = input.sourceMessages
|
|
45
|
+
.filter((message) => message.author.kind === "human" && messageLanguageEvidence(message).text.trim() !== "")
|
|
46
|
+
.at(-1);
|
|
47
|
+
if (evidence?.role !== "human" || latestHuman === undefined)
|
|
48
|
+
return;
|
|
49
|
+
writeCommunicationLanguage(db, {
|
|
50
|
+
owner: { kind: "project", ref: "current" },
|
|
51
|
+
language: decision,
|
|
52
|
+
activityRef: input.activityRef,
|
|
53
|
+
sourceCursor: { message_id: latestHuman.id, created_at: latestHuman.created_at },
|
|
54
|
+
now: input.now,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export function logLanguageContext(logger, context, anchors, decision) {
|
|
58
|
+
try {
|
|
59
|
+
logger?.info({
|
|
60
|
+
...anchors,
|
|
61
|
+
...languageDiagnosticFields(context),
|
|
62
|
+
language_phase: decision === undefined ? "prepared" : "selected",
|
|
63
|
+
...(decision === undefined
|
|
64
|
+
? {}
|
|
65
|
+
: { language_locale: decision.locale, language_basis: decision.basis }),
|
|
66
|
+
}, "Communication language context");
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
/* Diagnostics must not replace the result. */
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=communication-language.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createWorkflowInvocationRef } from "@tutti/shared/ids";
|
|
2
|
+
import { projectLanguageContext, logLanguageContext } from "./communication-language.js";
|
|
2
3
|
import { recordProjectTimelineEvent } from "../project-timeline/index.js";
|
|
3
4
|
import { syncProjectDocs } from "../workspace-ops/index.js";
|
|
4
5
|
import { logProcedureExecutionError } from "./procedure-logging.js";
|
|
@@ -62,7 +63,13 @@ export function startContextSyncAnalysisProcedure(input) {
|
|
|
62
63
|
error,
|
|
63
64
|
}),
|
|
64
65
|
execute: async (context) => {
|
|
66
|
+
const language = projectLanguageContext(input.store.db, "documents");
|
|
67
|
+
logLanguageContext(input.logger, language, {
|
|
68
|
+
workflow_ref: workflowRef,
|
|
69
|
+
activity_ref: context.activity_ref,
|
|
70
|
+
});
|
|
65
71
|
const workflowInput = {
|
|
72
|
+
communication_language: language,
|
|
66
73
|
workflow_ref: workflowRef,
|
|
67
74
|
activity_ref: context.activity_ref,
|
|
68
75
|
trigger: input.trigger,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { closeTaskBoundClarificationAsRetaskingRequired, openNextTaskBoundClarificationRound, } from "../collaboration-state/index.js";
|
|
2
|
+
import { withHostStoreTransaction } from "../store/index.js";
|
|
3
|
+
import { recordWorkflowLanguage } from "./communication-language.js";
|
|
2
4
|
import { clarificationInvalidates, taskRunInvalidates } from "./invalidations.js";
|
|
3
5
|
export function closeTaskBoundFollowUpAsRetaskingRequired(input) {
|
|
4
6
|
const closed = closeTaskBoundClarificationAsRetaskingRequired(input.store.db, {
|
|
@@ -73,10 +75,21 @@ export function applyFollowUpCheckOutput(input) {
|
|
|
73
75
|
summary: input.output.summary,
|
|
74
76
|
}, "Follow-up check completed");
|
|
75
77
|
if (input.output.decision === "needs_more_input") {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
const request = input.output.clarification_request_payload;
|
|
79
|
+
const opened = withHostStoreTransaction(input.store.db, (tx) => {
|
|
80
|
+
recordWorkflowLanguage(tx, {
|
|
81
|
+
owner: { kind: "task", ref: input.followUpInput.task_id },
|
|
82
|
+
...(input.output.communication_language === undefined
|
|
83
|
+
? {}
|
|
84
|
+
: { language: input.output.communication_language }),
|
|
85
|
+
activityRef: input.activityRef,
|
|
86
|
+
now: input.now,
|
|
87
|
+
});
|
|
88
|
+
return openNextTaskBoundClarificationRound(tx, {
|
|
89
|
+
continue_from_round_id: input.roundId,
|
|
90
|
+
request_payload: request,
|
|
91
|
+
now: input.now,
|
|
92
|
+
});
|
|
80
93
|
});
|
|
81
94
|
const nextRoundId = opened.round.id;
|
|
82
95
|
input.events.publish({
|
|
@@ -129,6 +142,14 @@ export function applyFollowUpCheckOutput(input) {
|
|
|
129
142
|
});
|
|
130
143
|
}
|
|
131
144
|
const summary = input.output.summary.trim().length === 0 ? "Follow-up accepted." : input.output.summary;
|
|
145
|
+
recordWorkflowLanguage(input.store.db, {
|
|
146
|
+
owner: { kind: "task", ref: input.followUpInput.task_id },
|
|
147
|
+
...(input.output.communication_language === undefined
|
|
148
|
+
? {}
|
|
149
|
+
: { language: input.output.communication_language }),
|
|
150
|
+
activityRef: input.activityRef,
|
|
151
|
+
now: input.now,
|
|
152
|
+
});
|
|
132
153
|
return {
|
|
133
154
|
kind: "completed",
|
|
134
155
|
summary,
|