@xfey/tutti 0.1.78 → 0.1.80
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 +1 -1
- package/dist/chat-assistant/index.d.ts +11 -1
- package/dist/chat-assistant/index.js +61 -7
- package/dist/checks/index.d.ts +22 -0
- package/dist/checks/index.js +110 -4
- package/dist/control-plane/follow-up-start.d.ts +1 -0
- package/dist/control-plane/follow-up-start.js +3 -1
- package/dist/control-plane/index.d.ts +6 -1
- package/dist/control-plane/index.js +72 -11
- package/dist/control-plane/procedure-logging.js +24 -67
- package/dist/control-plane/project-context-bootstrap.js +7 -1
- package/dist/control-plane/reference-summary-refresh.js +1 -1
- package/dist/control-plane/run-result-recording.js +2 -1
- package/dist/control-plane/task-compile-start.d.ts +1 -0
- package/dist/control-plane/task-compile-start.js +11 -1
- package/dist/control-plane/workflows/openai.d.ts +6 -0
- package/dist/control-plane/workflows/openai.js +16 -3
- package/dist/procedure-engine/index.d.ts +1 -0
- package/dist/procedure-engine/index.js +7 -1
- package/dist/process-tree.d.ts +25 -0
- package/dist/process-tree.js +121 -0
- package/dist/providers/openai/app-server/invocation-coordinator.d.ts +28 -0
- package/dist/providers/openai/app-server/invocation-coordinator.js +104 -0
- package/dist/providers/openai/app-server/invocation-lifecycle.d.ts +45 -0
- package/dist/providers/openai/app-server/invocation-lifecycle.js +104 -0
- package/dist/providers/openai/app-server/json-rpc.d.ts +1 -1
- package/dist/providers/openai/app-server/json-rpc.js +9 -16
- package/dist/providers/openai/app-server/read-only-procedure.d.ts +8 -2
- package/dist/providers/openai/app-server/read-only-procedure.js +63 -11
- package/dist/providers/openai/app-server/smoke-types.d.ts +4 -2
- package/dist/providers/openai/app-server/smoke.js +41 -12
- package/dist/providers/openai/app-server/stale-temp-cleanup.d.ts +17 -0
- package/dist/providers/openai/app-server/stale-temp-cleanup.js +130 -0
- package/dist/providers/openai/app-server/workspace-write-run.d.ts +8 -2
- package/dist/providers/openai/app-server/workspace-write-run.js +63 -11
- package/dist/providers/openai/chat-assistant.d.ts +6 -0
- package/dist/providers/openai/chat-assistant.js +12 -0
- package/dist/providers/openai/index.d.ts +1 -0
- package/dist/providers/openai/index.js +1 -0
- package/dist/providers/openai/machine-local-files.d.ts +1 -1
- package/dist/providers/openai/machine-local-files.js +32 -9
- package/dist/providers/openai/provider-config.d.ts +5 -1
- package/dist/providers/openai/provider-config.js +25 -4
- package/dist/providers/openai/provider-models.d.ts +41 -0
- package/dist/providers/openai/provider-models.js +82 -0
- package/dist/providers/openai/provider-setup.js +5 -2
- package/dist/run-engine/index.js +2 -1
- package/dist/run-pipeline/artifact-applicability.js +2 -2
- package/dist/run-pipeline/openai.d.ts +9 -0
- package/dist/run-pipeline/openai.js +26 -26
- package/dist/run-pipeline/task-run-invocation.js +6 -0
- package/dist/runtime-failures.d.ts +3 -0
- package/dist/runtime-failures.js +22 -0
- package/dist/server-shell/cli/host-lifecycle.js +1 -1
- package/dist/server-shell/cli/host-project-runtime-lifecycle.d.ts +43 -0
- package/dist/server-shell/cli/host-project-runtime-lifecycle.js +54 -0
- package/dist/server-shell/cli/host-server-runtime.d.ts +1 -1
- package/dist/server-shell/cli/host-server-runtime.js +140 -15
- package/dist/server-shell/http/create-server.d.ts +3 -0
- package/dist/server-shell/http/create-server.js +10 -4
- package/dist/server-shell/http/routes/project-api/invalidations.d.ts +1 -0
- package/dist/server-shell/http/routes/project-api/invalidations.js +4 -5
- package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +38 -14
- package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.js +26 -1
- package/dist/server-shell/http/routes/project-api/openapi.d.ts +38 -14
- package/dist/server-shell/http/routes/project-api/schemas.d.ts +8 -2
- package/dist/server-shell/http/routes/project-api/schemas.js +2 -0
- package/dist/server-shell/http/routes/project-api/session-requirements.d.ts +1 -0
- package/dist/server-shell/http/routes/project-api/session-requirements.js +7 -0
- package/dist/server-shell/http/routes/project-api/system-routes.js +96 -5
- package/dist/server-shell/http/routes/project-api/types.d.ts +10 -1
- package/dist/server-shell/http/routes/project-api/workspace-projections.js +4 -2
- package/dist/server-shell/http/routes/project-api.d.ts +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/provider-config.d.ts +36 -0
- package/node_modules/@tutti/shared/dist/schemas/api/provider-config.js +58 -1
- package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +9 -1
- package/package.json +1 -1
- package/web/assets/{homepage-motion-scene-sNI8fBH3.js → homepage-motion-scene-DXzmjALa.js} +1 -1
- package/web/assets/index-9gApbtPC.js +69 -0
- package/web/assets/index-BKeSEhsn.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-BUfAlA_H.css +0 -1
- package/web/assets/index-d-q9yZ09.js +0 -69
|
@@ -2,6 +2,8 @@ import type { ProjectId } from "@tutti/shared/ids";
|
|
|
2
2
|
import { type ReadOnlyChatAssistantModel } from "../../chat-assistant/index.js";
|
|
3
3
|
import { type OpenAiProviderConfig } from "./provider-config.js";
|
|
4
4
|
import type { CodexAppServerAgentContextRuntime } from "./app-server/skills.js";
|
|
5
|
+
import type { CodexAppServerInvocationCoordinator } from "./app-server/invocation-coordinator.js";
|
|
6
|
+
import type { CodexAppServerLifecycleIncident } from "./app-server/invocation-lifecycle.js";
|
|
5
7
|
import type { ProviderUsageRecorder } from "../../provider-usage/index.js";
|
|
6
8
|
export type OpenAiReadOnlyChatAssistantModelOptions = {
|
|
7
9
|
config: OpenAiProviderConfig;
|
|
@@ -16,6 +18,8 @@ export type OpenAiReadOnlyChatAssistantModelOptions = {
|
|
|
16
18
|
turnTimeoutMs?: number;
|
|
17
19
|
baseEnv?: NodeJS.ProcessEnv;
|
|
18
20
|
recordUsage?: ProviderUsageRecorder;
|
|
21
|
+
invocationCoordinator?: CodexAppServerInvocationCoordinator;
|
|
22
|
+
onLifecycleIncident?: (incident: CodexAppServerLifecycleIncident) => void;
|
|
19
23
|
};
|
|
20
24
|
export type ProjectOpenAiReadOnlyChatAssistantModelOptions = {
|
|
21
25
|
tuttiHome: string;
|
|
@@ -23,6 +27,8 @@ export type ProjectOpenAiReadOnlyChatAssistantModelOptions = {
|
|
|
23
27
|
workspaceRoot: string;
|
|
24
28
|
agentContext?: CodexAppServerAgentContextRuntime;
|
|
25
29
|
recordUsage?: ProviderUsageRecorder;
|
|
30
|
+
invocationCoordinator?: CodexAppServerInvocationCoordinator;
|
|
31
|
+
onLifecycleIncident?: (incident: CodexAppServerLifecycleIncident) => void;
|
|
26
32
|
};
|
|
27
33
|
export type ReadOnlyChatAssistantModelResolution = {
|
|
28
34
|
kind: "ready";
|
|
@@ -31,6 +31,12 @@ export function createOpenAiReadOnlyChatAssistantModel(options) {
|
|
|
31
31
|
: { requestTimeoutMs: options.requestTimeoutMs }),
|
|
32
32
|
...(options.turnTimeoutMs === undefined ? {} : { turnTimeoutMs: options.turnTimeoutMs }),
|
|
33
33
|
...(options.baseEnv === undefined ? {} : { baseEnv: options.baseEnv }),
|
|
34
|
+
...(options.invocationCoordinator === undefined
|
|
35
|
+
? {}
|
|
36
|
+
: { invocationCoordinator: options.invocationCoordinator }),
|
|
37
|
+
...(options.onLifecycleIncident === undefined
|
|
38
|
+
? {}
|
|
39
|
+
: { onLifecycleIncident: options.onLifecycleIncident }),
|
|
34
40
|
});
|
|
35
41
|
if (options.recordUsage !== undefined && result.usage !== undefined) {
|
|
36
42
|
options.recordUsage({
|
|
@@ -65,6 +71,12 @@ export function resolveProjectOpenAiReadOnlyChatAssistantModel(options) {
|
|
|
65
71
|
userSkillsRoot: getProjectUserSkillsRoot(options.tuttiHome, options.projectId),
|
|
66
72
|
...(options.agentContext === undefined ? {} : { agentContext: options.agentContext }),
|
|
67
73
|
...(options.recordUsage === undefined ? {} : { recordUsage: options.recordUsage }),
|
|
74
|
+
...(options.invocationCoordinator === undefined
|
|
75
|
+
? {}
|
|
76
|
+
: { invocationCoordinator: options.invocationCoordinator }),
|
|
77
|
+
...(options.onLifecycleIncident === undefined
|
|
78
|
+
? {}
|
|
79
|
+
: { onLifecycleIncident: options.onLifecycleIncident }),
|
|
68
80
|
}),
|
|
69
81
|
};
|
|
70
82
|
}
|
|
@@ -8,6 +8,7 @@ export * from "./credential-store.js";
|
|
|
8
8
|
export * from "./credential-validation.js";
|
|
9
9
|
export * from "./model-config.js";
|
|
10
10
|
export * from "./provider-config.js";
|
|
11
|
+
export * from "./provider-models.js";
|
|
11
12
|
export * from "./provider-setup.js";
|
|
12
13
|
export * from "./sdk-procedure-runner.js";
|
|
13
14
|
export * from "./token-usage.js";
|
|
@@ -8,6 +8,7 @@ export * from "./credential-store.js";
|
|
|
8
8
|
export * from "./credential-validation.js";
|
|
9
9
|
export * from "./model-config.js";
|
|
10
10
|
export * from "./provider-config.js";
|
|
11
|
+
export * from "./provider-models.js";
|
|
11
12
|
export * from "./provider-setup.js";
|
|
12
13
|
export * from "./sdk-procedure-runner.js";
|
|
13
14
|
export * from "./token-usage.js";
|
|
@@ -8,7 +8,7 @@ export type JsonFileReadResult = {
|
|
|
8
8
|
};
|
|
9
9
|
export declare function ensurePrivateDirectory(directoryPath: string): void;
|
|
10
10
|
export declare function writePrivateJsonFile(filePath: string, value: unknown): void;
|
|
11
|
-
export declare function
|
|
11
|
+
export declare function writePrivateTextFileAtomically(filePath: string, value: string): void;
|
|
12
12
|
export declare function readJsonFile(filePath: string): JsonFileReadResult;
|
|
13
13
|
export declare function readTextFile(filePath: string): {
|
|
14
14
|
kind: "missing";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
1
|
+
import { chmodSync, closeSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
3
4
|
function isErrnoException(error) {
|
|
4
5
|
return error instanceof Error && "code" in error;
|
|
5
6
|
}
|
|
@@ -15,13 +16,35 @@ export function writePrivateJsonFile(filePath, value) {
|
|
|
15
16
|
});
|
|
16
17
|
chmodSync(filePath, 0o600);
|
|
17
18
|
}
|
|
18
|
-
export function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
export function writePrivateTextFileAtomically(filePath, value) {
|
|
20
|
+
const directoryPath = dirname(filePath);
|
|
21
|
+
ensurePrivateDirectory(directoryPath);
|
|
22
|
+
const temporaryPath = join(directoryPath, `.${basename(filePath)}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`);
|
|
23
|
+
let descriptor;
|
|
24
|
+
try {
|
|
25
|
+
descriptor = openSync(temporaryPath, "wx", 0o600);
|
|
26
|
+
writeFileSync(descriptor, value, { encoding: "utf8" });
|
|
27
|
+
closeSync(descriptor);
|
|
28
|
+
descriptor = undefined;
|
|
29
|
+
renameSync(temporaryPath, filePath);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (descriptor !== undefined) {
|
|
33
|
+
try {
|
|
34
|
+
closeSync(descriptor);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Preserve the original write or close failure.
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
unlinkSync(temporaryPath);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// The temporary file may already have been renamed or never created.
|
|
45
|
+
}
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
25
48
|
}
|
|
26
49
|
export function readJsonFile(filePath) {
|
|
27
50
|
let raw;
|
|
@@ -3,7 +3,7 @@ import { type OpenAiCredentialRef } from "./credential-store.js";
|
|
|
3
3
|
import type { OPENAI_API_KEY_AUTH_KIND, OPENAI_PROVIDER } from "./model-config.js";
|
|
4
4
|
import { OpenAiProviderConfigError } from "./provider-config-errors.js";
|
|
5
5
|
export { OpenAiProviderConfigError } from "./provider-config-errors.js";
|
|
6
|
-
export { parseOpenAiProviderConfig
|
|
6
|
+
export { parseOpenAiProviderConfig } from "./provider-config-shape.js";
|
|
7
7
|
export type OpenAiProviderConfig = {
|
|
8
8
|
provider: typeof OPENAI_PROVIDER;
|
|
9
9
|
auth_kind: typeof OPENAI_API_KEY_AUTH_KIND;
|
|
@@ -58,6 +58,10 @@ export type OpenAiProviderConfigResolution = {
|
|
|
58
58
|
};
|
|
59
59
|
export declare function resolveTuttiHome(input: string | undefined, cwd?: string): string;
|
|
60
60
|
export declare function getProjectProviderConfigPath(tuttiHome: string, projectId: ProjectId): string;
|
|
61
|
+
export declare function withProjectOpenAiProviderConfigLock<T>(options: {
|
|
62
|
+
tuttiHome: string;
|
|
63
|
+
projectId: ProjectId;
|
|
64
|
+
}, operation: () => Promise<T>): Promise<T>;
|
|
61
65
|
export declare function readProjectOpenAiProviderConfig(tuttiHome: string, projectId: ProjectId): ProviderConfigReadResult<OpenAiProviderConfig>;
|
|
62
66
|
export declare function resolveOpenAiProviderConfig(options: {
|
|
63
67
|
tuttiHome: string;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { isAbsolute, join, resolve } from "node:path";
|
|
3
|
-
import { openAiCredentialExists
|
|
4
|
-
import { readTextFile,
|
|
3
|
+
import { openAiCredentialExists } from "./credential-store.js";
|
|
4
|
+
import { readTextFile, writePrivateTextFileAtomically } from "./machine-local-files.js";
|
|
5
5
|
import { OpenAiProviderConfigError } from "./provider-config-errors.js";
|
|
6
6
|
import { parseOpenAiProviderConfig } from "./provider-config-shape.js";
|
|
7
7
|
import { formatProjectOpenAiProviderConfig, parseTomlOpenAiSection, parseTuttiConfigToml, } from "./provider-config-toml.js";
|
|
8
8
|
export { OpenAiProviderConfigError } from "./provider-config-errors.js";
|
|
9
|
-
export { parseOpenAiProviderConfig
|
|
9
|
+
export { parseOpenAiProviderConfig } from "./provider-config-shape.js";
|
|
10
10
|
export function resolveTuttiHome(input, cwd = process.cwd()) {
|
|
11
11
|
if (input === undefined || input.trim() === "") {
|
|
12
12
|
return join(homedir(), ".tutti");
|
|
@@ -23,6 +23,27 @@ export function resolveTuttiHome(input, cwd = process.cwd()) {
|
|
|
23
23
|
export function getProjectProviderConfigPath(tuttiHome, projectId) {
|
|
24
24
|
return join(tuttiHome, "projects", projectId, "config.toml");
|
|
25
25
|
}
|
|
26
|
+
const providerConfigMutationTails = new Map();
|
|
27
|
+
export async function withProjectOpenAiProviderConfigLock(options, operation) {
|
|
28
|
+
const key = getProjectProviderConfigPath(options.tuttiHome, options.projectId);
|
|
29
|
+
const previous = providerConfigMutationTails.get(key) ?? Promise.resolve();
|
|
30
|
+
let release = () => { };
|
|
31
|
+
const current = new Promise((resolve) => {
|
|
32
|
+
release = resolve;
|
|
33
|
+
});
|
|
34
|
+
const tail = previous.then(() => current);
|
|
35
|
+
providerConfigMutationTails.set(key, tail);
|
|
36
|
+
await previous;
|
|
37
|
+
try {
|
|
38
|
+
return await operation();
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
release();
|
|
42
|
+
if (providerConfigMutationTails.get(key) === tail) {
|
|
43
|
+
providerConfigMutationTails.delete(key);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
26
47
|
export function readProjectOpenAiProviderConfig(tuttiHome, projectId) {
|
|
27
48
|
const sourcePath = getProjectProviderConfigPath(tuttiHome, projectId);
|
|
28
49
|
const readResult = readTextFile(sourcePath);
|
|
@@ -77,7 +98,7 @@ export function resolveOpenAiProviderConfig(options) {
|
|
|
77
98
|
};
|
|
78
99
|
}
|
|
79
100
|
export function writeProjectOpenAiProviderConfig(tuttiHome, projectId, config) {
|
|
80
|
-
|
|
101
|
+
writePrivateTextFileAtomically(getProjectProviderConfigPath(tuttiHome, projectId), formatProjectOpenAiProviderConfig(config));
|
|
81
102
|
}
|
|
82
103
|
export function createRedactedOpenAiProviderConfigProjection(config) {
|
|
83
104
|
const projection = {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ProjectId } from "@tutti/shared/ids";
|
|
2
|
+
import { type OpenAiCredentialValidationFailureReason, type OpenAiCredentialValidationInput, type OpenAiCredentialValidationResult, type OpenAiModelDiscoveryInput, type OpenAiModelDiscoveryResult } from "./credential-validation.js";
|
|
3
|
+
import { type RedactedOpenAiProviderConfigProjection } from "./provider-config.js";
|
|
4
|
+
export type ProjectOpenAiProviderUnavailableReason = "provider_not_configured" | "provider_config_invalid" | "provider_credential_missing";
|
|
5
|
+
export type ProjectOpenAiModelDiscoveryResult = OpenAiModelDiscoveryResult | {
|
|
6
|
+
kind: "unavailable";
|
|
7
|
+
reason: ProjectOpenAiProviderUnavailableReason;
|
|
8
|
+
};
|
|
9
|
+
export type UpdateProjectOpenAiDefaultModelResult = {
|
|
10
|
+
kind: "updated";
|
|
11
|
+
projection: RedactedOpenAiProviderConfigProjection;
|
|
12
|
+
} | {
|
|
13
|
+
kind: "unchanged";
|
|
14
|
+
projection: RedactedOpenAiProviderConfigProjection;
|
|
15
|
+
} | {
|
|
16
|
+
kind: "stale_default_model";
|
|
17
|
+
current_default_model: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "validation_failed";
|
|
20
|
+
reason: OpenAiCredentialValidationFailureReason;
|
|
21
|
+
retryable: boolean;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "provider_unavailable";
|
|
24
|
+
reason: ProjectOpenAiProviderUnavailableReason;
|
|
25
|
+
};
|
|
26
|
+
export type DiscoverProjectOpenAiModelsOptions = {
|
|
27
|
+
tuttiHome: string;
|
|
28
|
+
projectId: ProjectId;
|
|
29
|
+
discoverModels?: (input: OpenAiModelDiscoveryInput) => Promise<OpenAiModelDiscoveryResult>;
|
|
30
|
+
};
|
|
31
|
+
export type UpdateProjectOpenAiDefaultModelOptions = {
|
|
32
|
+
tuttiHome: string;
|
|
33
|
+
projectId: ProjectId;
|
|
34
|
+
defaultModel: string;
|
|
35
|
+
expectedDefaultModel: string;
|
|
36
|
+
now?: () => Date;
|
|
37
|
+
validateCredential?: (input: OpenAiCredentialValidationInput) => Promise<OpenAiCredentialValidationResult>;
|
|
38
|
+
};
|
|
39
|
+
export declare function discoverProjectOpenAiModels(options: DiscoverProjectOpenAiModelsOptions): Promise<ProjectOpenAiModelDiscoveryResult>;
|
|
40
|
+
export declare function updateProjectOpenAiDefaultModel(options: UpdateProjectOpenAiDefaultModelOptions): Promise<UpdateProjectOpenAiDefaultModelResult>;
|
|
41
|
+
//# sourceMappingURL=provider-models.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { readOpenAiApiKeyCredential } from "./credential-store.js";
|
|
2
|
+
import { discoverOpenAiModels, validateOpenAiCredential, } from "./credential-validation.js";
|
|
3
|
+
import { createRedactedOpenAiProviderConfigProjection, readProjectOpenAiProviderConfig, withProjectOpenAiProviderConfigLock, writeProjectOpenAiProviderConfig, } from "./provider-config.js";
|
|
4
|
+
function providerConfigSnapshot(options) {
|
|
5
|
+
const config = readProjectOpenAiProviderConfig(options.tuttiHome, options.projectId);
|
|
6
|
+
if (config.kind === "missing") {
|
|
7
|
+
return { kind: "unavailable", reason: "provider_not_configured" };
|
|
8
|
+
}
|
|
9
|
+
if (config.kind === "invalid") {
|
|
10
|
+
return { kind: "unavailable", reason: "provider_config_invalid" };
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const credential = readOpenAiApiKeyCredential(options.tuttiHome, options.projectId, config.config.credential_ref);
|
|
14
|
+
return { kind: "ready", config: config.config, apiKey: credential.secret };
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return { kind: "unavailable", reason: "provider_credential_missing" };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function providerInput(config, apiKey) {
|
|
21
|
+
return {
|
|
22
|
+
apiKey,
|
|
23
|
+
...(config.api_base_url === undefined ? {} : { apiBaseUrl: config.api_base_url }),
|
|
24
|
+
...(config.organization_id === undefined ? {} : { organizationId: config.organization_id }),
|
|
25
|
+
...(config.openai_project_id === undefined
|
|
26
|
+
? {}
|
|
27
|
+
: { openAiProjectId: config.openai_project_id }),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export async function discoverProjectOpenAiModels(options) {
|
|
31
|
+
return await withProjectOpenAiProviderConfigLock(options, async () => {
|
|
32
|
+
const snapshot = providerConfigSnapshot(options);
|
|
33
|
+
if (snapshot.kind === "unavailable") {
|
|
34
|
+
return snapshot;
|
|
35
|
+
}
|
|
36
|
+
return await (options.discoverModels ?? discoverOpenAiModels)(providerInput(snapshot.config, snapshot.apiKey));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export async function updateProjectOpenAiDefaultModel(options) {
|
|
40
|
+
return await withProjectOpenAiProviderConfigLock(options, async () => {
|
|
41
|
+
const snapshot = providerConfigSnapshot(options);
|
|
42
|
+
if (snapshot.kind === "unavailable") {
|
|
43
|
+
return { kind: "provider_unavailable", reason: snapshot.reason };
|
|
44
|
+
}
|
|
45
|
+
if (snapshot.config.default_model !== options.expectedDefaultModel) {
|
|
46
|
+
return {
|
|
47
|
+
kind: "stale_default_model",
|
|
48
|
+
current_default_model: snapshot.config.default_model,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (snapshot.config.default_model === options.defaultModel) {
|
|
52
|
+
return {
|
|
53
|
+
kind: "unchanged",
|
|
54
|
+
projection: createRedactedOpenAiProviderConfigProjection(snapshot.config),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const validator = options.validateCredential ??
|
|
58
|
+
((input) => validateOpenAiCredential(input, options.now === undefined ? {} : { now: options.now }));
|
|
59
|
+
const validation = await validator({
|
|
60
|
+
...providerInput(snapshot.config, snapshot.apiKey),
|
|
61
|
+
defaultModel: options.defaultModel,
|
|
62
|
+
});
|
|
63
|
+
if (validation.kind === "failed") {
|
|
64
|
+
return {
|
|
65
|
+
kind: "validation_failed",
|
|
66
|
+
reason: validation.reason,
|
|
67
|
+
retryable: validation.retryable,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const config = {
|
|
71
|
+
...snapshot.config,
|
|
72
|
+
default_model: validation.default_model,
|
|
73
|
+
validated_at: validation.validated_at,
|
|
74
|
+
};
|
|
75
|
+
writeProjectOpenAiProviderConfig(options.tuttiHome, options.projectId, config);
|
|
76
|
+
return {
|
|
77
|
+
kind: "updated",
|
|
78
|
+
projection: createRedactedOpenAiProviderConfigProjection(config),
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=provider-models.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createOpenAiCredentialRef, createRedactedOpenAiApiKeyLabel, isOpenAiApiKey, writeOpenAiApiKeyCredential, } from "./credential-store.js";
|
|
2
2
|
import { validateOpenAiCredential, } from "./credential-validation.js";
|
|
3
3
|
import { DEFAULT_OPENAI_MODEL, OPENAI_API_KEY_AUTH_KIND, OPENAI_PROVIDER } from "./model-config.js";
|
|
4
|
-
import { createRedactedOpenAiProviderConfigProjection, writeProjectOpenAiProviderConfig, } from "./provider-config.js";
|
|
4
|
+
import { createRedactedOpenAiProviderConfigProjection, withProjectOpenAiProviderConfigLock, writeProjectOpenAiProviderConfig, } from "./provider-config.js";
|
|
5
5
|
function trimOptional(value) {
|
|
6
6
|
const trimmed = value?.trim();
|
|
7
7
|
return trimmed === undefined || trimmed === "" ? undefined : trimmed;
|
|
@@ -32,7 +32,7 @@ function validationInput(options) {
|
|
|
32
32
|
}
|
|
33
33
|
return input;
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
async function configureProjectOpenAiProviderUnlocked(options) {
|
|
36
36
|
const apiKey = options.apiKey.trim();
|
|
37
37
|
if (!isOpenAiApiKey(apiKey)) {
|
|
38
38
|
return {
|
|
@@ -85,4 +85,7 @@ export async function configureProjectOpenAiProvider(options) {
|
|
|
85
85
|
projection: createRedactedOpenAiProviderConfigProjection(config),
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
|
+
export async function configureProjectOpenAiProvider(options) {
|
|
89
|
+
return await withProjectOpenAiProviderConfigLock(options, () => configureProjectOpenAiProviderUnlocked(options));
|
|
90
|
+
}
|
|
88
91
|
//# sourceMappingURL=provider-setup.js.map
|
package/dist/run-engine/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createActivityRef } from "@tutti/shared/ids";
|
|
2
|
+
import { safeRuntimeFailureSummary } from "../runtime-failures.js";
|
|
2
3
|
export class RunEngine {
|
|
3
4
|
active = null;
|
|
4
5
|
lastConclusion = null;
|
|
@@ -43,7 +44,7 @@ export class RunEngine {
|
|
|
43
44
|
catch (error) {
|
|
44
45
|
this.finishActive(activityRef, {
|
|
45
46
|
kind: "failed",
|
|
46
|
-
summary:
|
|
47
|
+
summary: safeRuntimeFailureSummary("Run", error, "run_failed"),
|
|
47
48
|
}, options.onTransition);
|
|
48
49
|
}
|
|
49
50
|
};
|
|
@@ -251,13 +251,13 @@ export async function runArtifactCandidateGate(options) {
|
|
|
251
251
|
try {
|
|
252
252
|
applicability = await judgeApplicability(options.judge ?? createDefaultJudge(options), input);
|
|
253
253
|
}
|
|
254
|
-
catch
|
|
254
|
+
catch {
|
|
255
255
|
return gateFailure({
|
|
256
256
|
reasonCode: "artifact_applicability_failed",
|
|
257
257
|
summary: "Tutti could not determine whether this candidate requires an Artifact.",
|
|
258
258
|
guidance: "Retry when the Artifact applicability provider is available.",
|
|
259
259
|
source: "applicability_judge",
|
|
260
|
-
detail:
|
|
260
|
+
detail: "Artifact applicability could not be determined by the provider.",
|
|
261
261
|
correctable: false,
|
|
262
262
|
});
|
|
263
263
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ProjectId } from "@tutti/shared/ids";
|
|
2
2
|
import type { ApprovalRequestManager } from "../approvals/index.js";
|
|
3
|
+
import { type CheckProcessCoordinator } from "../checks/index.js";
|
|
3
4
|
import type { ControlPlaneLogger, RunPipelineResolution, RunPipelineRunner } from "../control-plane/index.js";
|
|
4
5
|
import type { ProviderUsageRecorder } from "../provider-usage/index.js";
|
|
5
6
|
import type { OpenAiStructuredOutputClient } from "../providers/openai/sdk-procedure-runner.js";
|
|
@@ -8,6 +9,8 @@ import { type ArtifactApplicabilityJudge } from "./artifact-applicability.js";
|
|
|
8
9
|
import { type OpenAiProviderConfig } from "../providers/openai/provider-config.js";
|
|
9
10
|
import { type CodexLinuxSandboxReadiness } from "../providers/openai/app-server/linux-sandbox-readiness.js";
|
|
10
11
|
import type { CodexAppServerAgentContextRuntime } from "../providers/openai/app-server/skills.js";
|
|
12
|
+
import type { CodexAppServerInvocationCoordinator } from "../providers/openai/app-server/invocation-coordinator.js";
|
|
13
|
+
import type { CodexAppServerLifecycleIncident } from "../providers/openai/app-server/invocation-lifecycle.js";
|
|
11
14
|
export { TASK_RUN_OUTPUT_SCHEMA } from "./task-run-output.js";
|
|
12
15
|
export type OpenAiRunPipelineRunnerOptions = {
|
|
13
16
|
config: OpenAiProviderConfig;
|
|
@@ -23,9 +26,12 @@ export type OpenAiRunPipelineRunnerOptions = {
|
|
|
23
26
|
requestTimeoutMs?: number;
|
|
24
27
|
turnTimeoutMs?: number;
|
|
25
28
|
checkTimeoutMs?: number;
|
|
29
|
+
checkProcessCoordinator?: CheckProcessCoordinator;
|
|
26
30
|
baseEnv?: NodeJS.ProcessEnv;
|
|
27
31
|
logger?: ControlPlaneLogger | undefined;
|
|
28
32
|
recordUsage?: ProviderUsageRecorder;
|
|
33
|
+
invocationCoordinator?: CodexAppServerInvocationCoordinator;
|
|
34
|
+
onLifecycleIncident?: (incident: CodexAppServerLifecycleIncident) => void;
|
|
29
35
|
artifactApplicabilityClient?: OpenAiStructuredOutputClient;
|
|
30
36
|
artifactApplicabilityJudge?: ArtifactApplicabilityJudge;
|
|
31
37
|
validateArtifactCandidate?: (options: ValidateCandidateArtifactOptions) => Promise<CandidateArtifactValidationResult>;
|
|
@@ -40,6 +46,9 @@ export type ProjectOpenAiRunPipelineRunnerOptions = {
|
|
|
40
46
|
approvalManager?: ApprovalRequestManager;
|
|
41
47
|
logger?: ControlPlaneLogger | undefined;
|
|
42
48
|
recordUsage?: ProviderUsageRecorder;
|
|
49
|
+
invocationCoordinator?: CodexAppServerInvocationCoordinator;
|
|
50
|
+
checkProcessCoordinator?: CheckProcessCoordinator;
|
|
51
|
+
onLifecycleIncident?: (incident: CodexAppServerLifecycleIncident) => void;
|
|
43
52
|
};
|
|
44
53
|
export declare function createOpenAiRunPipelineRunner(options: OpenAiRunPipelineRunnerOptions): RunPipelineRunner;
|
|
45
54
|
export declare function resolveProjectOpenAiRunPipelineRunner(options: ProjectOpenAiRunPipelineRunnerOptions): RunPipelineResolution;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createNodeNpmCheckPlan, executeCheckPlan } from "../checks/index.js";
|
|
1
|
+
import { createNodeNpmCheckPlan, executeCheckPlan, } from "../checks/index.js";
|
|
2
2
|
import { runArtifactCandidateGate, } from "./artifact-applicability.js";
|
|
3
3
|
import { checkSkipFields, checkStatus, checksSummary, completedWithoutRepoChanges, docsStatusForChangedPaths, failedBeforeChecks, failedFinishedResult, promotionNotAttempted, promotionNotPromoted, successfulRunSummary, } from "./result-projections.js";
|
|
4
4
|
import { runWorkspaceWriteTask } from "./task-run-invocation.js";
|
|
@@ -13,28 +13,16 @@ function isRecord(value) {
|
|
|
13
13
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
14
|
}
|
|
15
15
|
function summarizeProviderError(error) {
|
|
16
|
-
const
|
|
17
|
-
error.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
error.details.stderr_excerpt === undefined || error.details.stderr_excerpt.trim() === ""
|
|
24
|
-
? ""
|
|
25
|
-
: `stderr=${error.details.stderr_excerpt}`,
|
|
26
|
-
].filter((part) => part !== "");
|
|
27
|
-
const summary = diagnosticParts.length === 0
|
|
28
|
-
? error.message
|
|
29
|
-
: `${error.message} (${diagnosticParts.join("; ")})`;
|
|
30
|
-
const looksLikeSchemaError = summary.includes("invalid_json_schema") ||
|
|
31
|
-
summary.includes("Invalid schema") ||
|
|
32
|
-
summary.includes("response_format");
|
|
16
|
+
const looksLikeSchemaError = error.details.turn_error_code === "invalid_json_schema" ||
|
|
17
|
+
error.message.includes("invalid_json_schema") ||
|
|
18
|
+
error.message.includes("Invalid schema") ||
|
|
19
|
+
error.message.includes("response_format");
|
|
20
|
+
const errorCode = looksLikeSchemaError
|
|
21
|
+
? "codex_app_server_output_schema_invalid"
|
|
22
|
+
: `codex_app_server_${error.code}`;
|
|
33
23
|
return failedBeforeChecks({
|
|
34
|
-
summary
|
|
35
|
-
errorCode
|
|
36
|
-
? "codex_app_server_output_schema_invalid"
|
|
37
|
-
: `codex_app_server_${error.code}`,
|
|
24
|
+
summary: `Provider execution failed before checks. (${errorCode})`,
|
|
25
|
+
errorCode,
|
|
38
26
|
retryable: !looksLikeSchemaError &&
|
|
39
27
|
(error.code === "app_server_protocol_error" ||
|
|
40
28
|
error.code === "turn_timeout" ||
|
|
@@ -44,13 +32,13 @@ function summarizeProviderError(error) {
|
|
|
44
32
|
}
|
|
45
33
|
function summarizeWorkspaceOpsError(error, changedPaths) {
|
|
46
34
|
return failedBeforeChecks({
|
|
47
|
-
summary: error.
|
|
35
|
+
summary: `Workspace operation failed before checks. (workspace_ops_${error.code})`,
|
|
48
36
|
errorCode: `workspace_ops_${error.code}`,
|
|
49
37
|
retryable: error.code === "conflict",
|
|
50
38
|
...(changedPaths === undefined ? {} : { changedPaths }),
|
|
51
39
|
promotion: error.code === "conflict"
|
|
52
|
-
? promotionNotPromoted("mainline_not_promotable",
|
|
53
|
-
: promotionNotAttempted("workspace_ops_failed",
|
|
40
|
+
? promotionNotPromoted("mainline_not_promotable", "Workspace state could not be accepted safely.")
|
|
41
|
+
: promotionNotAttempted("workspace_ops_failed", "Workspace operation failed before promotion."),
|
|
54
42
|
});
|
|
55
43
|
}
|
|
56
44
|
function runLineageFields(input) {
|
|
@@ -359,6 +347,9 @@ async function runTaskAttempt(options, input) {
|
|
|
359
347
|
workspaceRoot: options.workspaceRoot,
|
|
360
348
|
plan: checkPlan,
|
|
361
349
|
...(options.checkTimeoutMs === undefined ? {} : { timeoutMs: options.checkTimeoutMs }),
|
|
350
|
+
...(options.checkProcessCoordinator === undefined
|
|
351
|
+
? {}
|
|
352
|
+
: { processCoordinator: options.checkProcessCoordinator }),
|
|
362
353
|
}), (result) => ({ checks_status: result.status, command_count: result.commands.length }));
|
|
363
354
|
if (checks.status === "failed") {
|
|
364
355
|
const result = failedFinishedResult({
|
|
@@ -441,7 +432,7 @@ async function runTaskAttempt(options, input) {
|
|
|
441
432
|
return summarizeWorkspaceOpsError(error, changedPaths);
|
|
442
433
|
}
|
|
443
434
|
return failedBeforeChecks({
|
|
444
|
-
summary:
|
|
435
|
+
summary: "Run pipeline failed before checks. (run_pipeline_failed)",
|
|
445
436
|
errorCode: "run_pipeline_failed",
|
|
446
437
|
retryable: false,
|
|
447
438
|
...(changedPaths === undefined ? {} : { changedPaths }),
|
|
@@ -529,6 +520,15 @@ export function resolveProjectOpenAiRunPipelineRunner(options) {
|
|
|
529
520
|
...(options.promptsRoot === undefined ? {} : { promptsRoot: options.promptsRoot }),
|
|
530
521
|
...(options.logger === undefined ? {} : { logger: options.logger }),
|
|
531
522
|
...(options.recordUsage === undefined ? {} : { recordUsage: options.recordUsage }),
|
|
523
|
+
...(options.invocationCoordinator === undefined
|
|
524
|
+
? {}
|
|
525
|
+
: { invocationCoordinator: options.invocationCoordinator }),
|
|
526
|
+
...(options.checkProcessCoordinator === undefined
|
|
527
|
+
? {}
|
|
528
|
+
: { checkProcessCoordinator: options.checkProcessCoordinator }),
|
|
529
|
+
...(options.onLifecycleIncident === undefined
|
|
530
|
+
? {}
|
|
531
|
+
: { onLifecycleIncident: options.onLifecycleIncident }),
|
|
532
532
|
...(process.platform === "linux"
|
|
533
533
|
? { codexSandboxReadiness: () => inspectCodexLinuxSandboxReadiness() }
|
|
534
534
|
: {}),
|
|
@@ -111,6 +111,12 @@ export async function runWorkspaceWriteTask(options, input) {
|
|
|
111
111
|
: { requestTimeoutMs: options.requestTimeoutMs }),
|
|
112
112
|
...(options.turnTimeoutMs === undefined ? {} : { turnTimeoutMs: options.turnTimeoutMs }),
|
|
113
113
|
...(options.baseEnv === undefined ? {} : { baseEnv: options.baseEnv }),
|
|
114
|
+
...(options.invocationCoordinator === undefined
|
|
115
|
+
? {}
|
|
116
|
+
: { invocationCoordinator: options.invocationCoordinator }),
|
|
117
|
+
...(options.onLifecycleIncident === undefined
|
|
118
|
+
? {}
|
|
119
|
+
: { onLifecycleIncident: options.onLifecycleIncident }),
|
|
114
120
|
runtimeTelemetry: {
|
|
115
121
|
activityRef: input.activity_ref,
|
|
116
122
|
stageId: "provider_task_run",
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function stableRuntimeFailureCode(error: unknown, fallback: string): string;
|
|
2
|
+
export declare function safeRuntimeFailureSummary(subject: "Procedure" | "Run", error: unknown, fallbackCode: "procedure_failed" | "run_failed"): string;
|
|
3
|
+
//# sourceMappingURL=runtime-failures.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const STABLE_FAILURE_CODE = /^[a-z][a-z0-9_]{0,63}$/u;
|
|
2
|
+
export function stableRuntimeFailureCode(error, fallback) {
|
|
3
|
+
if (typeof error === "object" &&
|
|
4
|
+
error !== null &&
|
|
5
|
+
"code" in error &&
|
|
6
|
+
typeof error.code === "string" &&
|
|
7
|
+
STABLE_FAILURE_CODE.test(error.code)) {
|
|
8
|
+
return error.code;
|
|
9
|
+
}
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
export function safeRuntimeFailureSummary(subject, error, fallbackCode) {
|
|
13
|
+
const code = stableRuntimeFailureCode(error, fallbackCode);
|
|
14
|
+
if (code === "process_lifecycle_failed" || code === "process_tree_not_terminated") {
|
|
15
|
+
return `${subject} stopped because the provider process could not close safely. Restart the Host before retrying. (${code})`;
|
|
16
|
+
}
|
|
17
|
+
if (code === "admission_unavailable" || code === "admission_closed") {
|
|
18
|
+
return `${subject} stopped because provider execution is unavailable while the Host is closing. Retry after the Host is ready. (${code})`;
|
|
19
|
+
}
|
|
20
|
+
return `${subject} failed because the runtime returned an internal error. Retry the action. (${code})`;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=runtime-failures.js.map
|
|
@@ -393,7 +393,7 @@ export function formatLaunchLifecycleResult(result, options = {}) {
|
|
|
393
393
|
}
|
|
394
394
|
export async function waitForForegroundHostShutdown(handle, processLike = process) {
|
|
395
395
|
const stop = () => {
|
|
396
|
-
void handle.close();
|
|
396
|
+
void handle.close().catch(() => undefined);
|
|
397
397
|
};
|
|
398
398
|
processLike.once("SIGINT", stop);
|
|
399
399
|
processLike.once("SIGTERM", stop);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
type ControlPlaneLifecycleOwner = {
|
|
2
|
+
beginShutdown: () => void;
|
|
3
|
+
waitForIdle: () => Promise<void>;
|
|
4
|
+
};
|
|
5
|
+
type ChatAssistantLifecycleOwner = {
|
|
6
|
+
stopAccepting: () => void;
|
|
7
|
+
waitForIdle: () => Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
type InvocationLifecycleOwner = {
|
|
10
|
+
stopAccepting: () => void;
|
|
11
|
+
closeActive: () => Promise<void>;
|
|
12
|
+
waitForIdle: () => Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
type CheckLifecycleOwner = {
|
|
15
|
+
stopAccepting: () => void;
|
|
16
|
+
terminateActive: () => Promise<void>;
|
|
17
|
+
waitForIdle: () => Promise<void>;
|
|
18
|
+
};
|
|
19
|
+
type ApprovalLifecycleOwner = {
|
|
20
|
+
clearPending: () => void;
|
|
21
|
+
dispose: () => void;
|
|
22
|
+
};
|
|
23
|
+
export declare class HostProjectRuntimeShutdownError extends Error {
|
|
24
|
+
readonly code: "drain_timeout";
|
|
25
|
+
constructor(code: "drain_timeout", message: string);
|
|
26
|
+
}
|
|
27
|
+
export declare class HostProjectRuntimeLifecycle {
|
|
28
|
+
private readonly options;
|
|
29
|
+
private closePromise;
|
|
30
|
+
constructor(options: {
|
|
31
|
+
controlPlane: ControlPlaneLifecycleOwner;
|
|
32
|
+
chatAssistant: ChatAssistantLifecycleOwner;
|
|
33
|
+
invocationCoordinator: InvocationLifecycleOwner;
|
|
34
|
+
checkProcessCoordinator: CheckLifecycleOwner;
|
|
35
|
+
approvalManager: ApprovalLifecycleOwner;
|
|
36
|
+
gracefulDrainMs?: number;
|
|
37
|
+
forcedDrainMs?: number;
|
|
38
|
+
});
|
|
39
|
+
close(): Promise<void>;
|
|
40
|
+
private closeOnce;
|
|
41
|
+
}
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=host-project-runtime-lifecycle.d.ts.map
|