@xfey/tutti 0.1.108 → 0.1.110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +5 -1
  2. package/dist/chat-assistant/diagnostics.d.ts +18 -0
  3. package/dist/chat-assistant/diagnostics.js +27 -0
  4. package/dist/chat-assistant/index.d.ts +4 -0
  5. package/dist/chat-assistant/index.js +45 -21
  6. package/dist/chat-assistant/reference-freshness-smoke-cli.d.ts +2 -0
  7. package/dist/chat-assistant/reference-freshness-smoke-cli.js +40 -0
  8. package/dist/chat-assistant/reference-freshness-smoke.d.ts +23 -0
  9. package/dist/chat-assistant/reference-freshness-smoke.js +274 -0
  10. package/dist/collaboration-ingestion/index.d.ts +1 -1
  11. package/dist/collaboration-ingestion/index.js +1 -1
  12. package/dist/collaboration-ingestion/managed-reference-sources.d.ts +3 -1
  13. package/dist/collaboration-ingestion/managed-reference-sources.js +7 -0
  14. package/dist/collaboration-state/task-compile-context.d.ts +1 -1
  15. package/dist/providers/invocation-progress.d.ts +10 -0
  16. package/dist/providers/invocation-progress.js +9 -0
  17. package/dist/providers/openai/app-server/read-only-procedure.d.ts +2 -0
  18. package/dist/providers/openai/app-server/read-only-procedure.js +31 -2
  19. package/dist/providers/openai/app-server/skill-discovery.d.ts +10 -0
  20. package/dist/providers/openai/app-server/skill-discovery.js +29 -0
  21. package/dist/providers/openai/app-server/skills.d.ts +4 -16
  22. package/dist/providers/openai/app-server/skills.js +4 -29
  23. package/dist/providers/openai/app-server/workspace-write-run.js +13 -19
  24. package/dist/providers/openai/chat-assistant.js +12 -2
  25. package/dist/reference-access/message-context.d.ts +6 -0
  26. package/dist/reference-access/message-context.js +11 -0
  27. package/dist/reference-access/read-evidence.d.ts +2 -0
  28. package/dist/reference-access/read-evidence.js +8 -0
  29. package/dist/server-shell/cli/external-reference-runtime.js +1 -0
  30. package/dist/server-shell/cli/host-server-runtime.js +2 -0
  31. package/dist/server-shell/http/routes/agent-context-external-reference-routes.js +30 -4
  32. package/dist/server-shell/http/routes/agent-context-messages-routes.js +2 -0
  33. package/dist/server-shell/http/routes/agent-context-reference-routes.js +39 -3
  34. package/dist/server-shell/http/routes/agent-context-references.d.ts +11 -0
  35. package/dist/server-shell/http/routes/agent-context-references.js +14 -0
  36. package/dist/server-shell/http/static-web.js +10 -6
  37. package/node_modules/@tutti/shared/dist/schemas/api/clarifications.d.ts +2 -1
  38. package/node_modules/@tutti/shared/dist/schemas/api/external-references.d.ts +27 -4
  39. package/node_modules/@tutti/shared/dist/schemas/api/external-references.js +32 -3
  40. package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -1
  41. package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
  42. package/node_modules/@tutti/shared/dist/schemas/api/messages.d.ts +8 -4
  43. package/node_modules/@tutti/shared/dist/schemas/api/messages.js +2 -1
  44. package/node_modules/@tutti/shared/dist/schemas/api/platform-sessions.d.ts +5 -3
  45. package/node_modules/@tutti/shared/dist/schemas/api/platform-sessions.js +16 -1
  46. package/node_modules/@tutti/shared/dist/schemas/api/viewer-reference.d.ts +2 -1
  47. package/node_modules/@tutti/shared/dist/schemas/internal/external-references.d.ts +15 -2
  48. package/node_modules/@tutti/shared/dist/schemas/internal/external-references.js +4 -2
  49. package/node_modules/@tutti/shared/dist/schemas/internal/platform-integration.d.ts +6 -6
  50. package/package.json +1 -1
  51. package/prompts/chat-assistant.md +16 -15
  52. package/prompts/skills/README.md +1 -1
  53. package/prompts/skills/read-references/README.md +2 -2
  54. package/prompts/skills/read-references/SKILL.md +24 -37
  55. package/web/assets/{homepage-motion-scene-B6yPJnY8.js → homepage-motion-scene-rxYjg-zc.js} +1 -1
  56. package/web/assets/{index-aQnJ5JoI.js → index-BrKWdtKZ.js} +3 -3
  57. package/web/index.html +1 -1
@@ -24,7 +24,7 @@ export declare function completeTaskCompileContext(db: SqliteDatabase, input: Co
24
24
  task_changes: string[];
25
25
  receipts: {
26
26
  compiled_tasks?: {
27
- status: "failed" | "done" | "todo" | "running" | "pending";
27
+ status: "failed" | "done" | "pending" | "todo" | "running";
28
28
  task_id: string;
29
29
  title: string;
30
30
  summary: string;
@@ -0,0 +1,10 @@
1
+ export type ProviderInvocationProgress = {
2
+ stage: "provider_queued" | "provider_started" | "context_ready" | "skills_ready" | "turn_started" | "turn_completed" | "provider_failed";
3
+ invocation_ref?: string;
4
+ skill_count?: number;
5
+ completed_tool_count?: number;
6
+ reason_code?: "provider_unavailable" | "app_server_protocol_error" | "auth_failed" | "thread_start_failed" | "turn_failed" | "turn_timeout" | "unexpected_server_request" | "skill_selection_failed" | "output_parse_failed" | "output_validation_failed" | "file_read_not_observed" | "write_detected" | "secret_leak_detected" | "process_lifecycle_failed" | "admission_unavailable";
7
+ };
8
+ export type ProviderInvocationProgressObserver = (event: ProviderInvocationProgress) => void;
9
+ export declare function reportProviderInvocationProgress(observer: ProviderInvocationProgressObserver | undefined, event: ProviderInvocationProgress): void;
10
+ //# sourceMappingURL=invocation-progress.d.ts.map
@@ -0,0 +1,9 @@
1
+ export function reportProviderInvocationProgress(observer, event) {
2
+ try {
3
+ observer?.(event);
4
+ }
5
+ catch {
6
+ /* Diagnostics cannot change the provider outcome. */
7
+ }
8
+ }
9
+ //# sourceMappingURL=invocation-progress.js.map
@@ -1,3 +1,4 @@
1
+ import { type ProviderInvocationProgressObserver } from "../../invocation-progress.js";
1
2
  import { type CodexAppServerLifecycleIncident } from "./invocation-lifecycle.js";
2
3
  import { type CodexAppServerInvocationCoordinator, type CodexAppServerInvocationLease } from "./invocation-coordinator.js";
3
4
  import { type CodexAppServerRuntimeTelemetryOptions } from "./runtime-telemetry.js";
@@ -5,6 +6,7 @@ import { type CodexAppServerAgentContextRuntime, type CodexAppServerSkillSelecti
5
6
  import { type OpenAiTokenUsage } from "../token-usage.js";
6
7
  export type CodexAppServerWebSearchMode = "disabled" | "cached" | "live";
7
8
  export type CodexAppServerReadOnlyProcedureInput<TOutput> = {
9
+ onProgress?: ProviderInvocationProgressObserver;
8
10
  apiKey: string;
9
11
  apiBaseUrl?: string;
10
12
  defaultModel?: string;
@@ -1,7 +1,9 @@
1
1
  import { execFileSync, spawn } from "node:child_process";
2
2
  import { mkdirSync, mkdtempSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
- import { join } from "node:path";
4
+ import { dirname, join } from "node:path";
5
+ import { discoverCodexAppServerSkills } from "./skill-discovery.js";
6
+ import { reportProviderInvocationProgress, } from "../../invocation-progress.js";
5
7
  import { buildCodexAppServerProcessPlan } from "../codex-app-server.js";
6
8
  import { DEFAULT_OPENAI_MODEL } from "../model-config.js";
7
9
  import { CodexAppServerJsonRpcClient, CodexAppServerProtocolError } from "./json-rpc.js";
@@ -113,6 +115,7 @@ function structuredOutputDetails(client, text) {
113
115
  }
114
116
  export async function runCodexAppServerReadOnlyProcedure(options) {
115
117
  if (options.invocationCoordinator !== undefined && options.invocationLease === undefined) {
118
+ reportProviderInvocationProgress(options.onProgress, { stage: "provider_queued" });
116
119
  let lease;
117
120
  try {
118
121
  lease = await options.invocationCoordinator.acquire();
@@ -132,6 +135,7 @@ export async function runCodexAppServerReadOnlyProcedure(options) {
132
135
  lease.release();
133
136
  }
134
137
  }
138
+ reportProviderInvocationProgress(options.onProgress, { stage: "provider_started" });
135
139
  let skillInputs;
136
140
  let contextRuntimeHint;
137
141
  try {
@@ -167,6 +171,13 @@ export async function runCodexAppServerReadOnlyProcedure(options) {
167
171
  options.builtInSkillsRoot ?? "",
168
172
  ]));
169
173
  }
174
+ if (contextRuntimeHint !== undefined)
175
+ reportProviderInvocationProgress(options.onProgress, {
176
+ stage: "context_ready",
177
+ ...(contextRuntimeHint.invocation_ref === undefined
178
+ ? {}
179
+ : { invocation_ref: contextRuntimeHint.invocation_ref }),
180
+ });
170
181
  const model = options.defaultModel ?? DEFAULT_OPENAI_MODEL;
171
182
  const tempRoot = mkdtempSync(join(tmpdir(), "tutti-codex-readonly-procedure-"));
172
183
  const codexHome = options.session?.codexHome ?? join(tempRoot, "codex-home");
@@ -223,7 +234,7 @@ export async function runCodexAppServerReadOnlyProcedure(options) {
223
234
  options.userSkillsRoot ?? "",
224
235
  options.builtInSkillsRoot ?? "",
225
236
  ...(contextRuntimeHint?.sensitiveValues ?? []),
226
- ...skillInputs.map((skill) => skill.path),
237
+ ...skillInputs.flatMap((skill) => [skill.path, dirname(skill.path)]),
227
238
  ];
228
239
  const strictLeakCheckValues = [options.apiKey, ...(contextRuntimeHint?.sensitiveValues ?? [])];
229
240
  let primaryError;
@@ -248,6 +259,16 @@ export async function runCodexAppServerReadOnlyProcedure(options) {
248
259
  if (!isApiKeyAccount(loginResponse)) {
249
260
  throw new CodexAppServerReadOnlyProcedureError("auth_failed", "Codex app-server did not accept API key auth.");
250
261
  }
262
+ await discoverCodexAppServerSkills({
263
+ client,
264
+ cwd: appServerCwd,
265
+ skills: skillInputs,
266
+ requestTimeoutMs,
267
+ });
268
+ reportProviderInvocationProgress(options.onProgress, {
269
+ stage: "skills_ready",
270
+ skill_count: skillInputs.length,
271
+ });
251
272
  let threadId;
252
273
  if (options.session?.threadId === undefined) {
253
274
  const threadStartResponse = await client.request("thread/start", {
@@ -298,6 +319,7 @@ export async function runCodexAppServerReadOnlyProcedure(options) {
298
319
  modelProvider,
299
320
  outputSchema: options.outputSchema,
300
321
  }, requestTimeoutMs);
322
+ reportProviderInvocationProgress(options.onProgress, { stage: "turn_started" });
301
323
  const turnCompletedPromise = client.waitForNotification((notification) => notification.method === "turn/completed", turnTimeoutMs);
302
324
  const serverRequestPromise = client.waitForServerRequest(turnTimeoutMs);
303
325
  let completedOrRequest;
@@ -350,6 +372,10 @@ export async function runCodexAppServerReadOnlyProcedure(options) {
350
372
  const agentText = client.notifications
351
373
  .map((notification) => extractAgentText(notification))
352
374
  .join("");
375
+ reportProviderInvocationProgress(options.onProgress, {
376
+ stage: "turn_completed",
377
+ completed_tool_count: structuredOutputDetails(client, agentText).completed_tool_count ?? 0,
378
+ });
353
379
  let parsed;
354
380
  try {
355
381
  parsed = parseCodexAppServerStructuredJson(agentText);
@@ -408,6 +434,9 @@ export async function runCodexAppServerReadOnlyProcedure(options) {
408
434
  if (error instanceof CodexAppServerReadOnlyProcedureError) {
409
435
  primaryError = error;
410
436
  }
437
+ else if (error instanceof CodexAppServerSkillSelectionError) {
438
+ primaryError = new CodexAppServerReadOnlyProcedureError("skill_selection_failed", error.message);
439
+ }
411
440
  else if (error instanceof CodexAppServerProtocolError) {
412
441
  primaryError = new CodexAppServerReadOnlyProcedureError("app_server_protocol_error", redactWithKnownValues(error.message, sensitiveValues));
413
442
  }
@@ -0,0 +1,10 @@
1
+ import { type CodexAppServerSkillInput } from "./skills.js";
2
+ export declare function discoverCodexAppServerSkills(options: {
3
+ client: {
4
+ request: (method: string, params: unknown, timeoutMs: number) => Promise<unknown>;
5
+ };
6
+ cwd: string;
7
+ skills: CodexAppServerSkillInput[];
8
+ requestTimeoutMs: number;
9
+ }): Promise<void>;
10
+ //# sourceMappingURL=skill-discovery.d.ts.map
@@ -0,0 +1,29 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { isRecord } from "./runtime-helpers.js";
4
+ import { CodexAppServerSkillSelectionError } from "./skills.js";
5
+ // Use the pinned binary's protocol, not fields accepted only by a newer client.
6
+ // Extra roots are process-local; each invocation owns its app-server process.
7
+ export async function discoverCodexAppServerSkills(options) {
8
+ if (options.skills.length === 0)
9
+ return;
10
+ try {
11
+ const cwd = realpathSync(options.cwd);
12
+ await options.client.request("skills/extraRoots/set", { extraRoots: [...new Set(options.skills.map((skill) => dirname(skill.path)))] }, options.requestTimeoutMs);
13
+ const response = await options.client.request("skills/list", { cwds: [cwd], forceReload: true }, options.requestTimeoutMs);
14
+ const entry = isRecord(response) && Array.isArray(response.data)
15
+ ? response.data.find((entry) => isRecord(entry) && entry.cwd === cwd)
16
+ : undefined;
17
+ const discovered = isRecord(entry) && Array.isArray(entry.skills) ? entry.skills : [];
18
+ if (!options.skills.every((skill) => discovered.some((candidate) => isRecord(candidate) &&
19
+ candidate.name === skill.name &&
20
+ candidate.path === skill.path &&
21
+ candidate.enabled === true)))
22
+ throw new Error("Selected skill was not discovered");
23
+ }
24
+ catch {
25
+ // No discovered paths, parser errors or arbitrary RPC error bodies in diagnostics.
26
+ throw new CodexAppServerSkillSelectionError("skill_discovery_failed", "Codex app-server could not discover the selected skills.");
27
+ }
28
+ }
29
+ //# sourceMappingURL=skill-discovery.js.map
@@ -30,27 +30,21 @@ export type CodexAppServerAgentContextTokenRequest = {
30
30
  export type CodexAppServerAgentContextToken = {
31
31
  token: string;
32
32
  expires_at?: string;
33
+ invocation_ref?: string;
33
34
  };
34
35
  export type CodexAppServerAgentContextRuntime = {
35
36
  baseUrl: string;
36
37
  issueToken: (input: CodexAppServerAgentContextTokenRequest) => CodexAppServerAgentContextToken;
37
38
  };
38
- export type CodexAppServerSkillsListWarmup = {
39
- cwds: string[];
40
- forceReload: true;
41
- perCwdExtraUserRoots: Array<{
42
- cwd: string;
43
- extraUserRoots: string[];
44
- }>;
45
- };
46
39
  export type CodexAppServerContextRuntimeHint = {
47
40
  text: string;
48
41
  sensitiveValues: string[];
49
42
  scopes: CodexAppServerContextApiScope[];
43
+ invocation_ref?: string;
50
44
  };
51
45
  export declare class CodexAppServerSkillSelectionError extends Error {
52
- readonly code: "built_in_skill_not_found" | "built_in_skill_invalid";
53
- constructor(code: "built_in_skill_not_found" | "built_in_skill_invalid", message: string);
46
+ readonly code: "built_in_skill_not_found" | "built_in_skill_invalid" | "skill_discovery_failed";
47
+ constructor(code: "built_in_skill_not_found" | "built_in_skill_invalid" | "skill_discovery_failed", message: string);
54
48
  }
55
49
  export declare const CODEX_APP_SERVER_SKILL_SELECTIONS: Record<CodexAppServerSkillSelectionName, CodexAppServerSkillSelection>;
56
50
  export declare function resolveCodexAppServerSkillSelection(selectionName: CodexAppServerSkillSelectionName): CodexAppServerSkillSelection;
@@ -60,12 +54,6 @@ export declare function resolveCodexAppServerSkillInputs(options: {
60
54
  builtInSkillsRoot?: string;
61
55
  userSkillsRoot?: string;
62
56
  }): CodexAppServerSkillInput[];
63
- export declare function resolveCodexAppServerSkillsListWarmup(options: {
64
- cwd: string;
65
- selectionName?: CodexAppServerSkillSelectionName;
66
- selection?: CodexAppServerSkillSelection;
67
- userSkillsRoot?: string;
68
- }): CodexAppServerSkillsListWarmup | undefined;
69
57
  export declare function buildCodexAppServerTurnInput(options: {
70
58
  prompt: string;
71
59
  skills?: CodexAppServerSkillInput[];
@@ -1,4 +1,4 @@
1
- import { existsSync, statSync } from "node:fs";
1
+ import { existsSync, realpathSync, statSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { listUserSkills } from "../../../skills/index.js";
@@ -121,12 +121,12 @@ function resolveBuiltInSkill(options) {
121
121
  if (!statSync(skillMdPath).isFile()) {
122
122
  throw new CodexAppServerSkillSelectionError("built_in_skill_invalid", `Built-in skill is invalid: ${options.name}`);
123
123
  }
124
- return { name: options.name, path };
124
+ return { name: options.name, path: realpathSync(skillMdPath) };
125
125
  }
126
126
  function resolveUserSkill(options) {
127
127
  return {
128
128
  name: options.name,
129
- path: join(options.userSkillsRoot, options.name),
129
+ path: realpathSync(join(options.userSkillsRoot, options.name, "SKILL.md")),
130
130
  };
131
131
  }
132
132
  export function resolveCodexAppServerSkillSelection(selectionName) {
@@ -149,32 +149,6 @@ export function resolveCodexAppServerSkillInputs(options) {
149
149
  : [];
150
150
  return [...builtInSkills, ...userSkills];
151
151
  }
152
- export function resolveCodexAppServerSkillsListWarmup(options) {
153
- const selection = options.selection ??
154
- (options.selectionName === undefined
155
- ? {
156
- builtInSkillNames: [],
157
- userSkillMode: "disabled",
158
- contextApiScopes: [],
159
- }
160
- : resolveCodexAppServerSkillSelection(options.selectionName));
161
- if (selection.userSkillMode !== "all_enabled" || options.userSkillsRoot === undefined) {
162
- return undefined;
163
- }
164
- if (listUserSkills(options.userSkillsRoot).length === 0) {
165
- return undefined;
166
- }
167
- return {
168
- cwds: [options.cwd],
169
- forceReload: true,
170
- perCwdExtraUserRoots: [
171
- {
172
- cwd: options.cwd,
173
- extraUserRoots: [options.userSkillsRoot],
174
- },
175
- ],
176
- };
177
- }
178
152
  function createTextInputItem(text) {
179
153
  return {
180
154
  type: "text",
@@ -228,6 +202,7 @@ export function resolveCodexAppServerContextRuntimeHint(options) {
228
202
  }),
229
203
  sensitiveValues: [issued.token, options.agentContext.baseUrl],
230
204
  scopes: selection.contextApiScopes,
205
+ ...(issued.invocation_ref === undefined ? {} : { invocation_ref: issued.invocation_ref }),
231
206
  };
232
207
  }
233
208
  //# sourceMappingURL=skills.js.map
@@ -1,7 +1,8 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { mkdirSync, mkdtempSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
- import { join } from "node:path";
4
+ import { dirname, join } from "node:path";
5
+ import { discoverCodexAppServerSkills } from "./skill-discovery.js";
5
6
  import { buildCodexAppServerProcessPlan } from "../codex-app-server.js";
6
7
  import { DEFAULT_OPENAI_MODEL } from "../model-config.js";
7
8
  import { CodexAppServerJsonRpcClient, CodexAppServerProtocolError, } from "./json-rpc.js";
@@ -10,7 +11,7 @@ import { CodexAppServerAdmissionError, } from "./invocation-coordinator.js";
10
11
  import { createCodexAppServerRuntimeObserver, } from "./runtime-telemetry.js";
11
12
  import { createReadOnlySandboxPolicy, createWorkspaceWriteSandboxPolicy, } from "./sandbox-policy.js";
12
13
  import { buildCodexAppServerProviderConfigOverrides, resolveCodexAppServerModelProvider, } from "./provider-config.js";
13
- import { buildCodexAppServerTurnInput, resolveCodexAppServerContextRuntimeHint, resolveCodexAppServerSkillsListWarmup, resolveCodexAppServerSkillInputs, CodexAppServerSkillSelectionError, } from "./skills.js";
14
+ import { buildCodexAppServerTurnInput, resolveCodexAppServerContextRuntimeHint, resolveCodexAppServerSkillInputs, CodexAppServerSkillSelectionError, } from "./skills.js";
14
15
  import { compactText, containsKnownSecret, createCodexSpawnEnv, extractAgentText, extractServerRequestMethod, findJsonObjectCandidates, isApiKeyAccount, readThreadId, readTurnError, readTurnStatus, redactWithKnownValues, } from "./runtime-helpers.js";
15
16
  import { extractCodexAppServerTokenUsage } from "../token-usage.js";
16
17
  const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
@@ -170,7 +171,6 @@ export async function runCodexAppServerWorkspaceWriteRun(options) {
170
171
  }
171
172
  }
172
173
  let skillInputs;
173
- let skillsListWarmup;
174
174
  let contextRuntimeHint;
175
175
  try {
176
176
  skillInputs = resolveCodexAppServerSkillInputs({
@@ -182,13 +182,6 @@ export async function runCodexAppServerWorkspaceWriteRun(options) {
182
182
  ? {}
183
183
  : { builtInSkillsRoot: options.builtInSkillsRoot }),
184
184
  });
185
- skillsListWarmup = resolveCodexAppServerSkillsListWarmup({
186
- cwd: options.workspaceRoot,
187
- ...(options.skillSelectionName === undefined
188
- ? {}
189
- : { selectionName: options.skillSelectionName }),
190
- ...(options.userSkillsRoot === undefined ? {} : { userSkillsRoot: options.userSkillsRoot }),
191
- });
192
185
  contextRuntimeHint = resolveCodexAppServerContextRuntimeHint({
193
186
  ...(options.approvalContext?.activityRef === undefined
194
187
  ? {}
@@ -267,11 +260,7 @@ export async function runCodexAppServerWorkspaceWriteRun(options) {
267
260
  options.userSkillsRoot ?? "",
268
261
  options.builtInSkillsRoot ?? "",
269
262
  ...(contextRuntimeHint?.sensitiveValues ?? []),
270
- ...skillInputs.map((skill) => skill.path),
271
- ...(skillsListWarmup?.perCwdExtraUserRoots.flatMap((entry) => [
272
- entry.cwd,
273
- ...entry.extraUserRoots,
274
- ]) ?? []),
263
+ ...skillInputs.flatMap((skill) => [skill.path, dirname(skill.path)]),
275
264
  ];
276
265
  const strictLeakCheckValues = [options.apiKey, ...(contextRuntimeHint?.sensitiveValues ?? [])];
277
266
  let primaryError;
@@ -297,9 +286,12 @@ export async function runCodexAppServerWorkspaceWriteRun(options) {
297
286
  if (!isApiKeyAccount(loginResponse)) {
298
287
  throw new CodexAppServerWorkspaceWriteRunError("auth_failed", "Codex app-server did not accept API key auth.");
299
288
  }
300
- if (skillsListWarmup !== undefined) {
301
- await client.request("skills/list", skillsListWarmup, requestTimeoutMs);
302
- }
289
+ await discoverCodexAppServerSkills({
290
+ client,
291
+ cwd: options.workspaceRoot,
292
+ skills: skillInputs,
293
+ requestTimeoutMs,
294
+ });
303
295
  let threadId;
304
296
  if (options.session?.threadId === undefined) {
305
297
  const threadStartResponse = await client.request("thread/start", {
@@ -484,7 +476,9 @@ export async function runCodexAppServerWorkspaceWriteRun(options) {
484
476
  }
485
477
  else {
486
478
  const message = error instanceof Error ? error.message : String(error);
487
- primaryError = new CodexAppServerWorkspaceWriteRunError("app_server_protocol_error", redactWithKnownValues(message, sensitiveValues), { turn_start_state: turnStartState });
479
+ primaryError = new CodexAppServerWorkspaceWriteRunError(error instanceof CodexAppServerSkillSelectionError
480
+ ? "skill_selection_failed"
481
+ : "app_server_protocol_error", redactWithKnownValues(message, sensitiveValues), { turn_start_state: turnStartState });
488
482
  }
489
483
  }
490
484
  finally {
@@ -1,12 +1,14 @@
1
1
  import { CHAT_ASSISTANT_OUTPUT_SCHEMA, isChatAssistantOutput, } from "../../chat-assistant/index.js";
2
2
  import { readOpenAiApiKeyCredential } from "./credential-store.js";
3
- import { resolveOpenAiProviderConfig, } from "./provider-config.js";
4
- import { runCodexAppServerReadOnlyProcedure } from "./app-server/read-only-procedure.js";
3
+ import { resolveOpenAiProviderConfig } from "./provider-config.js";
4
+ import { CodexAppServerReadOnlyProcedureError, runCodexAppServerReadOnlyProcedure, } from "./app-server/read-only-procedure.js";
5
+ import { reportProviderInvocationProgress } from "../invocation-progress.js";
5
6
  import { getProjectUserSkillsRoot } from "../../skills/index.js";
6
7
  export function createOpenAiReadOnlyChatAssistantModel(options) {
7
8
  return {
8
9
  async answer(input) {
9
10
  const result = await runCodexAppServerReadOnlyProcedure({
11
+ ...(input.onProgress === undefined ? {} : { onProgress: input.onProgress }),
10
12
  apiKey: options.apiKey,
11
13
  workspaceRoot: options.workspaceRoot,
12
14
  prompt: input.prompt,
@@ -37,6 +39,14 @@ export function createOpenAiReadOnlyChatAssistantModel(options) {
37
39
  ...(options.onLifecycleIncident === undefined
38
40
  ? {}
39
41
  : { onLifecycleIncident: options.onLifecycleIncident }),
42
+ }).catch((error) => {
43
+ reportProviderInvocationProgress(input.onProgress, {
44
+ stage: "provider_failed",
45
+ reason_code: error instanceof CodexAppServerReadOnlyProcedureError
46
+ ? error.code
47
+ : "provider_unavailable",
48
+ });
49
+ throw error;
40
50
  });
41
51
  if (options.recordUsage !== undefined && result.usage !== undefined) {
42
52
  options.recordUsage({
@@ -0,0 +1,6 @@
1
+ import type { MessageProjection } from "@tutti/shared/schemas/api";
2
+ /** Location hints, not access grants or current-read evidence. Never copy private locators. */
3
+ export declare function externalReferenceMessageContext(refs: MessageProjection["refs"]): {
4
+ external_reference_sources?: string[];
5
+ };
6
+ //# sourceMappingURL=message-context.d.ts.map
@@ -0,0 +1,11 @@
1
+ /** Location hints, not access grants or current-read evidence. Never copy private locators. */
2
+ export function externalReferenceMessageContext(refs) {
3
+ const sources = [
4
+ ...(refs?.external_references?.map((source) => source.source_ref) ?? []),
5
+ ...(refs?.external_reference_reads ?? []),
6
+ ];
7
+ return sources.length === 0
8
+ ? {}
9
+ : { external_reference_sources: [...new Set(sources)].slice(0, 16) };
10
+ }
11
+ //# sourceMappingURL=message-context.js.map
@@ -1,6 +1,8 @@
1
1
  import type { SqliteDatabase } from "../store/index.js";
2
2
  import type { ReferenceCopy } from "./cache.js";
3
3
  import type { ReferenceInvocation } from "./service.js";
4
+ /** Prior answer context only: a source reference is not proof of freshness in a later turn. */
5
+ export declare function readExternalReferenceReadSources(db: SqliteDatabase, invocationRef: string): string[];
4
6
  /** Durable observation, separate from evictable cache metadata; no content or locator. */
5
7
  export declare function recordExternalReferenceRead(db: SqliteDatabase, invocation: ReferenceInvocation, copy: ReferenceCopy): void;
6
8
  //# sourceMappingURL=read-evidence.d.ts.map
@@ -1,3 +1,11 @@
1
+ /** Prior answer context only: a source reference is not proof of freshness in a later turn. */
2
+ export function readExternalReferenceReadSources(db, invocationRef) {
3
+ return db
4
+ .prepare(`SELECT source_ref FROM external_reference_reads
5
+ WHERE invocation_ref = ? ORDER BY checked_at, source_ref LIMIT 16`)
6
+ .all(invocationRef)
7
+ .map((row) => row.source_ref);
8
+ }
1
9
  /** Durable observation, separate from evictable cache metadata; no content or locator. */
2
10
  export function recordExternalReferenceRead(db, invocation, copy) {
3
11
  const invocationRef = invocation.reference_invocation_ref;
@@ -17,6 +17,7 @@ export function createExternalReferenceRuntime(options) {
17
17
  return requestExternalReferences({
18
18
  relayUrl: preparation.relay_url,
19
19
  body: {
20
+ supports_drive_files: true,
20
21
  project_identity: { source: "git_config", project_id: preparation.project_id },
21
22
  host_auth: { kind: "host_registration_secret_v1", secret: secret.secret },
22
23
  relay_project_ref: relay.relay_project_ref,
@@ -137,6 +137,7 @@ function createAgentContextRuntime(options) {
137
137
  return {
138
138
  token: token.token,
139
139
  expires_at: token.expires_at,
140
+ invocation_ref: token.reference_invocation_ref,
140
141
  };
141
142
  },
142
143
  };
@@ -419,6 +420,7 @@ export async function startForegroundHostServer(options) {
419
420
  }),
420
421
  });
421
422
  const chatAssistant = new ReadOnlyChatAssistant({
423
+ logger: createHostRuntimeLogger({ component: "chat-assistant", logFilePath, now }),
422
424
  project: chatAssistantProject,
423
425
  store,
424
426
  events: workspaceEvents,
@@ -16,20 +16,43 @@ export function registerAgentContextExternalReferenceRoutes(app, options) {
16
16
  }, async (request, reply) => {
17
17
  reply.header("cache-control", "no-store");
18
18
  const invocation = authorizeAgentContextRequest(request, options, "references:external");
19
+ const started = performance.now();
20
+ const diagnosticFields = {
21
+ action,
22
+ invocation_ref: invocation.reference_invocation_ref,
23
+ activity_ref: invocation.activity_ref,
24
+ };
25
+ request.log.info({ ...diagnosticFields, stage: "external_reference_request" }, "External Reference request started");
19
26
  const service = options.externalReferences;
20
- if (!service)
27
+ if (!service) {
28
+ request.log.info({
29
+ ...diagnosticFields,
30
+ stage: "external_reference_unavailable",
31
+ reason_code: "external_references_not_enabled",
32
+ }, "External Reference service unavailable");
21
33
  return { kind: "unavailable", reason: "external_references_not_enabled" };
34
+ }
22
35
  const sourceRef = request.query.source_ref;
23
36
  if (action === "read" &&
24
37
  (sourceRef === undefined || !/^external_ref_[a-f0-9]{32}$/u.test(sourceRef)))
25
38
  return { kind: "unavailable", reason: "reference_source_invalid" };
26
39
  try {
27
- if (action === "list")
28
- return { items: await service.list(invocation) };
40
+ if (action === "list") {
41
+ const items = await service.list(invocation);
42
+ request.log.info({
43
+ ...diagnosticFields,
44
+ stage: "external_reference_list",
45
+ source_count: items.length,
46
+ elapsed_ms: Math.round(performance.now() - started),
47
+ }, "External Reference sources listed");
48
+ return { items };
49
+ }
29
50
  const result = await service.read(invocation, sourceRef);
30
51
  const { copy } = result;
31
52
  request.log.info({
32
53
  stage: "external_reference_read",
54
+ action,
55
+ elapsed_ms: Math.round(performance.now() - started),
33
56
  invocation_ref: invocation.reference_invocation_ref,
34
57
  activity_ref: invocation.activity_ref,
35
58
  source_ref: copy.source_ref,
@@ -53,6 +76,7 @@ export function registerAgentContextExternalReferenceRoutes(app, options) {
53
76
  kind: text === undefined ? "local_file" : "text",
54
77
  ...copy,
55
78
  cache_hit: result.cache_hit,
79
+ provenance: { origin: "external_reference", verification: "this_invocation" },
56
80
  ...(safeText === undefined
57
81
  ? {}
58
82
  : {
@@ -70,7 +94,9 @@ export function registerAgentContextExternalReferenceRoutes(app, options) {
70
94
  ? "reference_cache_unsafe"
71
95
  : "reference_read_unavailable";
72
96
  request.log.warn({
73
- stage: "external_reference_read",
97
+ stage: "external_reference_unavailable",
98
+ action,
99
+ elapsed_ms: Math.round(performance.now() - started),
74
100
  invocation_ref: invocation.reference_invocation_ref,
75
101
  activity_ref: invocation.activity_ref,
76
102
  ...(sourceRef === undefined ? {} : { source_ref: sourceRef }),
@@ -1,4 +1,5 @@
1
1
  import { readMainChatMessageWindow } from "../../../collaboration-state/index.js";
2
+ import { externalReferenceMessageContext } from "../../../reference-access/message-context.js";
2
3
  import { authorizeAgentContextRequest } from "./agent-context-auth.js";
3
4
  import { AgentContextBadRequestError } from "./agent-context-errors.js";
4
5
  import { boundedLimit, queryString, truncateText } from "./agent-context-query.js";
@@ -44,6 +45,7 @@ export function registerAgentContextMessageRoutes(app, options) {
44
45
  },
45
46
  body: body.text,
46
47
  body_truncated: body.truncated,
48
+ ...externalReferenceMessageContext(message.refs),
47
49
  ...(message.refs?.reference_file === undefined
48
50
  ? {}
49
51
  : {
@@ -3,11 +3,17 @@ import { VIEWER_HARD_READ_LIMIT_BYTES, WorkspaceOpsError, readReferenceSummaryIn
3
3
  import { authorizeAgentContextRequest } from "./agent-context-auth.js";
4
4
  import { AgentContextBadRequestError } from "./agent-context-errors.js";
5
5
  import { boundedLimit, pageByOffset, queryString } from "./agent-context-query.js";
6
- import { mapReferenceUnavailableReason, referenceListItems, } from "./agent-context-references.js";
6
+ import { mapReferenceUnavailableReason, localReferenceProvenance, managedReferencePaths, referenceListItems, } from "./agent-context-references.js";
7
7
  import { AGENT_CONTEXT_BASE_PATH, REFERENCE_FILE_DEFAULT_MAX_BYTES, REFERENCE_FILE_MAX_BYTES, REFERENCES_LIMIT_DEFAULT, REFERENCES_LIMIT_MAX, requireWorkspaceRoot, } from "./agent-context-shared.js";
8
8
  export function registerAgentContextReferenceRoutes(app, options) {
9
9
  app.get(`${AGENT_CONTEXT_BASE_PATH}/references`, (request) => {
10
- authorizeAgentContextRequest(request, options, "references:list");
10
+ const invocation = authorizeAgentContextRequest(request, options, "references:list");
11
+ const started = performance.now();
12
+ request.log.info({
13
+ stage: "local_reference_request",
14
+ action: "list",
15
+ invocation_ref: invocation.reference_invocation_ref,
16
+ }, "Local Reference request started");
11
17
  const query = request.query;
12
18
  const limit = boundedLimit(query.limit, {
13
19
  default: REFERENCES_LIMIT_DEFAULT,
@@ -20,6 +26,12 @@ export function registerAgentContextReferenceRoutes(app, options) {
20
26
  ...(cursor === undefined ? {} : { cursor }),
21
27
  limit,
22
28
  });
29
+ request.log.info({
30
+ stage: "local_reference_list",
31
+ invocation_ref: invocation.reference_invocation_ref,
32
+ source_count: page.items.length,
33
+ elapsed_ms: Math.round(performance.now() - started),
34
+ }, "Local References listed; remote freshness not checked");
23
35
  return {
24
36
  items: page.items,
25
37
  page: {
@@ -29,7 +41,13 @@ export function registerAgentContextReferenceRoutes(app, options) {
29
41
  };
30
42
  });
31
43
  app.get(`${AGENT_CONTEXT_BASE_PATH}/references/file`, (request) => {
32
- authorizeAgentContextRequest(request, options, "references:read");
44
+ const invocation = authorizeAgentContextRequest(request, options, "references:read");
45
+ const started = performance.now();
46
+ const diagnosticFields = {
47
+ invocation_ref: invocation.reference_invocation_ref,
48
+ action: "read",
49
+ };
50
+ request.log.info({ ...diagnosticFields, stage: "local_reference_request" }, "Local Reference request started");
33
51
  const path = queryString(request.query.path);
34
52
  if (path === undefined || path.trim() === "") {
35
53
  throw new AgentContextBadRequestError("Reference path is required");
@@ -49,12 +67,21 @@ export function registerAgentContextReferenceRoutes(app, options) {
49
67
  entry: file.file,
50
68
  summaries: readReferenceSummaryIndex({ workspaceRoot: requireWorkspaceRoot(options) }),
51
69
  });
70
+ const provenance = localReferenceProvenance(file.file.path, managedReferencePaths(options));
71
+ request.log.info({
72
+ ...diagnosticFields,
73
+ stage: "local_reference_read",
74
+ ...provenance,
75
+ result_kind: file.content.kind,
76
+ elapsed_ms: Math.round(performance.now() - started),
77
+ }, "Local Reference read; remote freshness not checked");
52
78
  if (file.content.kind !== "text") {
53
79
  return {
54
80
  kind: "unavailable",
55
81
  path,
56
82
  reason: file.content.reason,
57
83
  summary,
84
+ provenance,
58
85
  };
59
86
  }
60
87
  return {
@@ -62,12 +89,21 @@ export function registerAgentContextReferenceRoutes(app, options) {
62
89
  path: file.file.path,
63
90
  name: file.file.name,
64
91
  summary,
92
+ provenance,
65
93
  text: redactText(file.content.text),
66
94
  truncated: file.content.truncated,
67
95
  bytes_read: file.content.bytes_returned,
68
96
  };
69
97
  }
70
98
  catch (error) {
99
+ request.log.warn({
100
+ ...diagnosticFields,
101
+ stage: "local_reference_unavailable",
102
+ reason_code: error instanceof WorkspaceOpsError
103
+ ? error.code
104
+ : "reference_read_unavailable",
105
+ elapsed_ms: Math.round(performance.now() - started),
106
+ }, "Local Reference read unavailable");
71
107
  if (error instanceof WorkspaceOpsError) {
72
108
  return {
73
109
  kind: "unavailable",