FN-8660: add credential instance selection persistence
Persist optional credential-instance selections across model configuration without changing runtime credential behavior. - Add credential instance IDs to global, project, task, preset, and workflow IR model lanes. - Preserve selected instance IDs through model resolution and task persistence. - Add PostgreSQL migration coverage, unit tests, documentation, and a minor changeset. Files changed: .../fn-8660-credential-instance-selection.md | 7 ++ docs/settings-reference.md | 17 +++++ .../core/src/__tests__/model-resolution.test.ts | 37 ++++++++++ .../credential-instance-selection.pg.test.ts | 81 +++++++++++++++++++++ .../postgres/settings-persistence.pg.test.ts | 83 ++++++++++++++++++++++ .../src/__tests__/workflow-ir-settings.test.ts | 66 +++++++++++++++++ packages/core/src/builtin-workflow-settings.ts | 41 +++++++++++ packages/core/src/model-resolution.ts | 57 ++++++++++++++- .../0039_fn_8660_credential_instance_selection.sql | 9 +++ packages/core/src/postgres/schema-applier.ts | 14 +++- packages/core/src/postgres/schema/project.ts | 4 ++ packages/core/src/settings-schema.ts | 25 +++++++ packages/core/src/store.ts | 2 +- .../core/src/task-store/archive-lifecycle-2.ts | 8 +++ .../core/src/task-store/branch-and-pr-entities.ts | 2 +- packages/core/src/task-store/persistence.ts | 8 +++ packages/core/src/task-store/serialization.ts | 6 ++ packages/core/src/task-store/settings-ops.ts | 30 ++++++++ packages/core/src/task-store/task-creation.ts | 18 ++++- packages/core/src/task-store/task-mutation-ops.ts | 6 +- packages/core/src/task-store/task-row-mappers.ts | 6 +- packages/core/src/task-store/task-update.ts | 24 +++++++ packages/core/src/types/archive-planning.ts | 5 ++ packages/core/src/types/settings-scope.ts | 40 +++++++++++ packages/core/src/types/task-core.ts | 30 ++++++++ packages/core/src/types/workflow-steps.ts | 9 +++ packages/core/src/workflow-ir.ts | 18 +++++ packages/core/src/workflow-settings.ts | 10 +++ 28 files changed, 650 insertions(+), 13 deletions(-) Fusion-Task-Id: FN-8660 Fusion-Task-Lineage: a3f625eb-018c-4084-954e-488b1d37691e Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8660-credential-instance-selection.md
Normal file
7
.changeset/fn-8660-credential-instance-selection.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Persist optional credential-instance selections across Fusion model configuration.
|
||||||
|
category: feature
|
||||||
|
dev: Adds *CredentialInstanceId settings, presets, task and workflow IR fields, settings-schema registration, and Postgres migration 0039; persisted-but-inert this slice.
|
||||||
@@ -1865,3 +1865,20 @@ Project-scoped ordered list of up to six mobile footer quick actions. The defaul
|
|||||||
### Plugin-provided MCP servers
|
### Plugin-provided MCP servers
|
||||||
|
|
||||||
Enabled plugins may declare `mcpServers` declaratively. The contribution is project-scoped: only plugins enabled in that project's plugin state participate. Resolution is global → enabled plugin declarations → project settings, by server `name`; later declarations win and a project `enabled:false` entry removes an inherited plugin server. The Global MCP card never includes plugin declarations. Project MCP UI identifies them as `plugin:<id>` and writes only project overrides or tombstones.
|
Enabled plugins may declare `mcpServers` declaratively. The contribution is project-scoped: only plugins enabled in that project's plugin state participate. Resolution is global → enabled plugin declarations → project settings, by server `name`; later declarations win and a project `enabled:false` entry removes an inherited plugin server. The Global MCP card never includes plugin declarations. Project MCP UI identifies them as `plugin:<id>` and writes only project overrides or tombstones.
|
||||||
|
|
||||||
|
### Credential instance companions
|
||||||
|
|
||||||
|
Every provider/model selection can now persist an optional `*CredentialInstanceId` companion: the
|
||||||
|
project/global default and fallback pairs, per-lane project/global pairs, selected-workflow lane
|
||||||
|
values, and task overrides. `ModelPreset` entries similarly accept
|
||||||
|
`executorCredentialInstanceId` and `validatorCredentialInstanceId`. These fields follow the same
|
||||||
|
scope and precedence as their sibling `*Provider`/`*ModelId` pair; the instance belongs only to the
|
||||||
|
winning pair and is omitted when unset.
|
||||||
|
|
||||||
|
Credential instance ids are validated when authored. Invalid values (including empty,
|
||||||
|
whitespace-only, bracket-containing, oversized, or non-string values) are rejected for project and
|
||||||
|
global settings, workflow setting values, workflow IR node overrides, and task writes. Settings
|
||||||
|
validation also visits every `modelPresets[]` element: one invalid executor or validator instance
|
||||||
|
id rejects the entire settings write without changing the stored presets. These values are
|
||||||
|
persisted-but-inert in this release; runtime credential resolution will consume them in the
|
||||||
|
follow-up runtime-resolution work.
|
||||||
|
|||||||
@@ -601,3 +601,40 @@ describe("model-resolution", () => {
|
|||||||
expect(applyTestModeOverrides(resolved, {})).toEqual(resolved);
|
expect(applyTestModeOverrides(resolved, {})).toEqual(resolved);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("credential instance selection", () => {
|
||||||
|
it("carries the winning pair's instance without mixing a losing tier", () => {
|
||||||
|
expect(resolveExecutionSettingsModel({
|
||||||
|
executionProvider: "project-provider",
|
||||||
|
executionModelId: "project-model",
|
||||||
|
executionCredentialInstanceId: "project-instance",
|
||||||
|
executionGlobalProvider: "global-provider",
|
||||||
|
executionGlobalModelId: "global-model",
|
||||||
|
executionGlobalCredentialInstanceId: "global-instance",
|
||||||
|
})).toEqual({ provider: "project-provider", modelId: "project-model", credentialInstanceId: "project-instance" });
|
||||||
|
|
||||||
|
expect(resolveExecutionSettingsModel({
|
||||||
|
executionProvider: "project-provider",
|
||||||
|
executionModelId: "project-model",
|
||||||
|
executionGlobalCredentialInstanceId: "losing-instance",
|
||||||
|
})).toEqual({ provider: "project-provider", modelId: "project-model" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("carries workflow, fallback, and task credential instances with their complete pairs", () => {
|
||||||
|
expect(resolvePlanningSettingsModel({
|
||||||
|
selectedWorkflowModelLanes: {
|
||||||
|
planningProvider: "workflow-provider",
|
||||||
|
planningModelId: "workflow-model",
|
||||||
|
planningCredentialInstanceId: "workflow-instance",
|
||||||
|
},
|
||||||
|
})).toEqual({ provider: "workflow-provider", modelId: "workflow-model", credentialInstanceId: "workflow-instance" });
|
||||||
|
expect(resolveValidatorFallbackModel({
|
||||||
|
validatorFallbackProvider: "fallback-provider",
|
||||||
|
validatorFallbackModelId: "fallback-model",
|
||||||
|
validatorFallbackCredentialInstanceId: "fallback-instance",
|
||||||
|
})).toEqual({ provider: "fallback-provider", modelId: "fallback-model", credentialInstanceId: "fallback-instance" });
|
||||||
|
expect(resolveTaskExecutionModel({
|
||||||
|
modelProvider: "task-provider", modelId: "task-model", credentialInstanceId: "task-instance",
|
||||||
|
})).toEqual({ provider: "task-provider", modelId: "task-model", credentialInstanceId: "task-instance" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* FNXC:CredentialInstanceSelection 2026-08-01-05:53:
|
||||||
|
* Credential-instance selection is persisted data in this slice. Exercise each task authoring
|
||||||
|
* route and lifecycle projection so a future runtime consumer receives only durable, validated ids.
|
||||||
|
*/
|
||||||
|
import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "vitest";
|
||||||
|
import {
|
||||||
|
createSharedPgTaskStoreTestHarness,
|
||||||
|
pgDescribe,
|
||||||
|
type SharedPgTaskStoreHarness,
|
||||||
|
} from "../../__test-utils__/pg-test-harness.js";
|
||||||
|
|
||||||
|
const credentialFields = {
|
||||||
|
credentialInstanceId: "executor-instance",
|
||||||
|
validatorCredentialInstanceId: "validator-instance",
|
||||||
|
planningCredentialInstanceId: "planning-instance",
|
||||||
|
mergerCredentialInstanceId: "merger-instance",
|
||||||
|
};
|
||||||
|
|
||||||
|
pgDescribe("credential-instance task persistence (PostgreSQL)", () => {
|
||||||
|
const h: SharedPgTaskStoreHarness = createSharedPgTaskStoreTestHarness({
|
||||||
|
prefix: "fusion_credential_instance_selection",
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeAll(h.beforeAll);
|
||||||
|
beforeEach(h.beforeEach);
|
||||||
|
afterEach(h.afterEach);
|
||||||
|
afterAll(h.afterAll);
|
||||||
|
|
||||||
|
it("round-trips all task fields through create, read, update, archive, restore, and branch-group reads", async () => {
|
||||||
|
const store = h.store();
|
||||||
|
const group = await store.ensureBranchGroupForSource("planning", "credential-selection", {
|
||||||
|
name: "credential selection group",
|
||||||
|
branchName: "fusion/credential-selection",
|
||||||
|
baseBranch: "main",
|
||||||
|
});
|
||||||
|
const created = await store.createTask({
|
||||||
|
description: "persist credential instances",
|
||||||
|
branchContext: { groupId: group.id, source: "planning", assignmentMode: "shared" },
|
||||||
|
...credentialFields,
|
||||||
|
} as never);
|
||||||
|
|
||||||
|
expect(await store.getTask(created.id)).toMatchObject(credentialFields);
|
||||||
|
expect(await store.listTasksByBranchGroup(group.id)).toEqual([
|
||||||
|
expect.objectContaining({ id: created.id, ...credentialFields }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const updatedFields = {
|
||||||
|
credentialInstanceId: "executor-updated",
|
||||||
|
validatorCredentialInstanceId: "validator-updated",
|
||||||
|
planningCredentialInstanceId: "planning-updated",
|
||||||
|
mergerCredentialInstanceId: "merger-updated",
|
||||||
|
};
|
||||||
|
await store.updateTask(created.id, updatedFields as never);
|
||||||
|
expect(await store.getTask(created.id)).toMatchObject(updatedFields);
|
||||||
|
|
||||||
|
await store.archiveTask(created.id, { cleanup: false } as never);
|
||||||
|
const restored = await store.unarchiveTask(created.id);
|
||||||
|
expect(restored).toMatchObject(updatedFields);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps absent task fields absent after a database read", async () => {
|
||||||
|
const task = await h.store().createTask({ description: "no credential instance" });
|
||||||
|
const read = await h.store().getTask(task.id);
|
||||||
|
for (const key of Object.keys(credentialFields)) expect(read).not.toHaveProperty(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects malformed create, update, and atomic mutation inputs without partial persistence", async () => {
|
||||||
|
const store = h.store();
|
||||||
|
for (const invalid of ["", " ", "bad[id]", "x".repeat(257), 42]) {
|
||||||
|
await expect(store.createTask({ description: "invalid create", credentialInstanceId: invalid } as never)).rejects.toThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
const task = await store.createTask({ description: "invalid update", ...credentialFields } as never);
|
||||||
|
for (const invalid of ["", " ", "bad[id]", "x".repeat(257), 42]) {
|
||||||
|
await expect(store.updateTask(task.id, { validatorCredentialInstanceId: invalid } as never)).rejects.toThrow();
|
||||||
|
await expect(store.updateTaskAtomic(task.id, () => ({ planningCredentialInstanceId: invalid } as never))).rejects.toThrow();
|
||||||
|
expect(await store.getTask(task.id)).toMatchObject(credentialFields);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -11,6 +11,14 @@ import {
|
|||||||
createSharedPgTaskStoreTestHarness,
|
createSharedPgTaskStoreTestHarness,
|
||||||
type SharedPgTaskStoreHarness,
|
type SharedPgTaskStoreHarness,
|
||||||
} from "../../__test-utils__/pg-test-harness.js";
|
} from "../../__test-utils__/pg-test-harness.js";
|
||||||
|
import { GLOBAL_SETTINGS_KEYS, PROJECT_SETTINGS_KEYS } from "../../settings-schema.js";
|
||||||
|
|
||||||
|
const credentialLaneKeys = [
|
||||||
|
["defaultProvider", "defaultCredentialInstanceId"],
|
||||||
|
["executionGlobalProvider", "executionGlobalCredentialInstanceId"],
|
||||||
|
["titleSummarizerProvider", "titleSummarizerCredentialInstanceId"],
|
||||||
|
["defaultProviderOverride", "defaultCredentialInstanceIdOverride"],
|
||||||
|
] as const;
|
||||||
|
|
||||||
const pgTest = pgDescribe;
|
const pgTest = pgDescribe;
|
||||||
|
|
||||||
@@ -60,4 +68,79 @@ pgTest("VAL-CROSS-004: Settings persistence (PostgreSQL)", () => {
|
|||||||
const settings2 = await store.getSettings();
|
const settings2 = await store.getSettings();
|
||||||
expect(settings2.maxConcurrentTasks).toBe(5);
|
expect(settings2.maxConcurrentTasks).toBe(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("persists global, project, lane, fallback, and preset credential instances", async () => {
|
||||||
|
const store = h.store();
|
||||||
|
await store.updateGlobalSettings({
|
||||||
|
defaultCredentialInstanceId: "global-default",
|
||||||
|
executionGlobalCredentialInstanceId: "global-execution",
|
||||||
|
fallbackCredentialInstanceId: "global-fallback",
|
||||||
|
});
|
||||||
|
await store.updateSettings({
|
||||||
|
defaultCredentialInstanceIdOverride: "project-default",
|
||||||
|
titleSummarizerCredentialInstanceId: "project-title",
|
||||||
|
titleSummarizerFallbackCredentialInstanceId: "project-title-fallback",
|
||||||
|
modelPresets: [{
|
||||||
|
id: "credential-preset",
|
||||||
|
name: "Credential preset",
|
||||||
|
executorProvider: "anthropic",
|
||||||
|
executorModelId: "claude",
|
||||||
|
executorCredentialInstanceId: "preset-executor",
|
||||||
|
validatorProvider: "openai",
|
||||||
|
validatorModelId: "gpt",
|
||||||
|
validatorCredentialInstanceId: "preset-validator",
|
||||||
|
}],
|
||||||
|
} as never);
|
||||||
|
|
||||||
|
const settings = await store.getSettings();
|
||||||
|
expect(settings).toMatchObject({
|
||||||
|
defaultCredentialInstanceId: "global-default",
|
||||||
|
executionGlobalCredentialInstanceId: "global-execution",
|
||||||
|
fallbackCredentialInstanceId: "global-fallback",
|
||||||
|
defaultCredentialInstanceIdOverride: "project-default",
|
||||||
|
titleSummarizerCredentialInstanceId: "project-title",
|
||||||
|
titleSummarizerFallbackCredentialInstanceId: "project-title-fallback",
|
||||||
|
});
|
||||||
|
expect(settings.modelPresets).toEqual([expect.objectContaining({
|
||||||
|
executorCredentialInstanceId: "preset-executor",
|
||||||
|
validatorCredentialInstanceId: "preset-validator",
|
||||||
|
})]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("persists a workflow-lane credential instance through its scoped settings row", async () => {
|
||||||
|
const store = h.store();
|
||||||
|
const projectId = store.getWorkflowSettingsProjectId();
|
||||||
|
await store.updateWorkflowSettingValues("builtin:coding", projectId, {
|
||||||
|
executionCredentialInstanceId: "workflow-execution",
|
||||||
|
});
|
||||||
|
expect(await store.getWorkflowSettingValuesAsync("builtin:coding", projectId))
|
||||||
|
.toMatchObject({ executionCredentialInstanceId: "workflow-execution" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps credential-instance keys in the same settings scope as their provider", () => {
|
||||||
|
for (const [providerKey, instanceKey] of credentialLaneKeys) {
|
||||||
|
expect(GLOBAL_SETTINGS_KEYS.includes(providerKey as never)).toBe(GLOBAL_SETTINGS_KEYS.includes(instanceKey as never));
|
||||||
|
expect(PROJECT_SETTINGS_KEYS.includes(providerKey as never)).toBe(PROJECT_SETTINGS_KEYS.includes(instanceKey as never));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects invalid global/project ids and atomically preserves stored presets", async () => {
|
||||||
|
const store = h.store();
|
||||||
|
const priorPresets = [{ id: "prior", name: "Prior", executorProvider: "anthropic", executorModelId: "claude" }];
|
||||||
|
await store.updateSettings({ modelPresets: priorPresets } as never);
|
||||||
|
|
||||||
|
await expect(store.updateGlobalSettings({ defaultCredentialInstanceId: "bad[id]" } as never)).rejects.toThrow();
|
||||||
|
await expect(store.updateSettings({ titleSummarizerCredentialInstanceId: "bad[id]" } as never)).rejects.toThrow();
|
||||||
|
await expect(store.updateSettings({ modelPresets: [
|
||||||
|
...priorPresets,
|
||||||
|
{ id: "bad-executor", name: "Bad", executorCredentialInstanceId: "bad[id]" },
|
||||||
|
] } as never)).rejects.toThrow();
|
||||||
|
expect((await store.getSettings()).modelPresets).toEqual(priorPresets);
|
||||||
|
|
||||||
|
await expect(store.updateSettings({ modelPresets: [
|
||||||
|
...priorPresets,
|
||||||
|
{ id: "bad-validator", name: "Bad", validatorCredentialInstanceId: " " },
|
||||||
|
] } as never)).rejects.toThrow();
|
||||||
|
expect((await store.getSettings()).modelPresets).toEqual(priorPresets);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -279,3 +279,69 @@ describe("built-in workflow settings parity anchor (U1, R4)", () => {
|
|||||||
expect((DEFAULT_PROJECT_SETTINGS as Record<string, unknown>).buildTimeoutMs).toBe(300_000);
|
expect((DEFAULT_PROJECT_SETTINGS as Record<string, unknown>).buildTimeoutMs).toBe(300_000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("credential-instance workflow settings", () => {
|
||||||
|
it("accepts valid ids and atomically rejects malformed companion values", async () => {
|
||||||
|
const { validateSettingValuePatch } = await import("../workflow-settings.js");
|
||||||
|
const declarations: WorkflowSettingDefinition[] = [
|
||||||
|
{ id: "executionCredentialInstanceId", name: "Instance", type: "string" },
|
||||||
|
];
|
||||||
|
expect(validateSettingValuePatch(declarations, { executionCredentialInstanceId: "work" }).accepted)
|
||||||
|
.toEqual({ executionCredentialInstanceId: "work" });
|
||||||
|
for (const executionCredentialInstanceId of ["", " ", "bad[id]", "x".repeat(257), 42]) {
|
||||||
|
const rejected = validateSettingValuePatch(declarations, { executionCredentialInstanceId });
|
||||||
|
expect(rejected.accepted).toEqual({});
|
||||||
|
expect(rejected.rejections).toHaveLength(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("credential-instance workflow node config", () => {
|
||||||
|
const valid = {
|
||||||
|
version: "v2" as const, name: "credential-instance", columns: [],
|
||||||
|
nodes: [{ id: "start", kind: "start" }, { id: "end", kind: "end", config: { credentialInstanceId: "work" } }],
|
||||||
|
edges: [{ from: "start", to: "end" }],
|
||||||
|
};
|
||||||
|
|
||||||
|
it("round-trips valid ids and preserves omission", () => {
|
||||||
|
expect(parseWorkflowIr(valid).nodes[1]?.config?.credentialInstanceId).toBe("work");
|
||||||
|
const omitted = parseWorkflowIr({ ...valid, nodes: [{ id: "start", kind: "start" }, { id: "end", kind: "end" }] });
|
||||||
|
expect(omitted.nodes[1]?.config).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects malformed and non-string ids", () => {
|
||||||
|
for (const credentialInstanceId of ["", " ", "bad[id]", "x".repeat(257), 42]) {
|
||||||
|
expect(() => parseWorkflowIr({
|
||||||
|
...valid,
|
||||||
|
nodes: [{ id: "start", kind: "start" }, { id: "end", kind: "end", config: { credentialInstanceId } }],
|
||||||
|
})).toThrow(WorkflowIrError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("validates credential instances inside foreach templates", () => {
|
||||||
|
const template = {
|
||||||
|
version: "v2" as const,
|
||||||
|
name: "credential-instance-template",
|
||||||
|
columns: [],
|
||||||
|
artifacts: [{ key: "plan" }],
|
||||||
|
nodes: [
|
||||||
|
{ id: "start", kind: "start" },
|
||||||
|
{ id: "steps", kind: "parse-steps", config: { artifact: "plan", parser: "step-headings" } },
|
||||||
|
{ id: "each", kind: "foreach", config: { source: "task-steps", template: {
|
||||||
|
nodes: [{ id: "execute", kind: "step-execute", config: { credentialInstanceId: "template-instance" } }],
|
||||||
|
edges: [],
|
||||||
|
} } },
|
||||||
|
{ id: "end", kind: "end" },
|
||||||
|
],
|
||||||
|
edges: [{ from: "start", to: "steps" }, { from: "steps", to: "each" }, { from: "each", to: "end" }],
|
||||||
|
};
|
||||||
|
expect(parseWorkflowIr(template).nodes[2]?.config?.template?.nodes[0]?.config?.credentialInstanceId).toBe("template-instance");
|
||||||
|
expect(() => parseWorkflowIr({
|
||||||
|
...template,
|
||||||
|
nodes: [...template.nodes.slice(0, 2), { ...template.nodes[2], config: {
|
||||||
|
...template.nodes[2].config,
|
||||||
|
template: { nodes: [{ id: "execute", kind: "step-execute", config: { credentialInstanceId: "bad[id]" } }], edges: [] },
|
||||||
|
} }, template.nodes[3]],
|
||||||
|
})).toThrow(WorkflowIrError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -208,6 +208,11 @@ export const BUILTIN_MOVED_WORKFLOW_SETTINGS: WorkflowSettingDefinition[] = [
|
|||||||
// workflow settings. `reflectionEnabled` is kept because executor.ts reads it
|
// workflow settings. `reflectionEnabled` is kept because executor.ts reads it
|
||||||
// (gate for reflection tools).
|
// (gate for reflection tools).
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FNXC:CredentialInstanceSelection 2026-08-01-05:38:
|
||||||
|
* Workflow lanes persist optional credential-instance ids beside their provider/model pairs.
|
||||||
|
* Values are validated at write time and remain inert until runtime credential resolution.
|
||||||
|
*/
|
||||||
// ── Per-phase model lanes ──────────────────────────────────────────────
|
// ── Per-phase model lanes ──────────────────────────────────────────────
|
||||||
// Legacy defaults are all `undefined`; `default` is omitted so resolution
|
// Legacy defaults are all `undefined`; `default` is omitted so resolution
|
||||||
// falls through to the global lane / project default (KTD-7).
|
// falls through to the global lane / project default (KTD-7).
|
||||||
@@ -224,6 +229,12 @@ export const BUILTIN_MOVED_WORKFLOW_SETTINGS: WorkflowSettingDefinition[] = [
|
|||||||
type: "string",
|
type: "string",
|
||||||
description: "Provider for the execution phase. Empty falls through to the global lane.",
|
description: "Provider for the execution phase. Empty falls through to the global lane.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "executionCredentialInstanceId",
|
||||||
|
name: "Execution credential instance",
|
||||||
|
type: "string",
|
||||||
|
description: "Optional credential instance for the execution model pair.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "executionModelId",
|
id: "executionModelId",
|
||||||
name: "Execution model",
|
name: "Execution model",
|
||||||
@@ -248,6 +259,12 @@ export const BUILTIN_MOVED_WORKFLOW_SETTINGS: WorkflowSettingDefinition[] = [
|
|||||||
type: "string",
|
type: "string",
|
||||||
description: "Fallback provider for the execution phase.",
|
description: "Fallback provider for the execution phase.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "executionFallbackCredentialInstanceId",
|
||||||
|
name: "Execution fallback credential instance",
|
||||||
|
type: "string",
|
||||||
|
description: "Optional credential instance for the execution fallback model pair.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "executionFallbackModelId",
|
id: "executionFallbackModelId",
|
||||||
name: "Executor fallback model",
|
name: "Executor fallback model",
|
||||||
@@ -267,6 +284,12 @@ export const BUILTIN_MOVED_WORKFLOW_SETTINGS: WorkflowSettingDefinition[] = [
|
|||||||
type: "string",
|
type: "string",
|
||||||
description: "Provider for the planning phase. Empty falls through to the global lane.",
|
description: "Provider for the planning phase. Empty falls through to the global lane.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "planningCredentialInstanceId",
|
||||||
|
name: "Planning credential instance",
|
||||||
|
type: "string",
|
||||||
|
description: "Optional credential instance for the planning model pair.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "planningModelId",
|
id: "planningModelId",
|
||||||
name: "Planning model",
|
name: "Planning model",
|
||||||
@@ -286,6 +309,12 @@ export const BUILTIN_MOVED_WORKFLOW_SETTINGS: WorkflowSettingDefinition[] = [
|
|||||||
type: "string",
|
type: "string",
|
||||||
description: "Fallback provider for the planning phase.",
|
description: "Fallback provider for the planning phase.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "planningFallbackCredentialInstanceId",
|
||||||
|
name: "Planning fallback credential instance",
|
||||||
|
type: "string",
|
||||||
|
description: "Optional credential instance for the planning fallback model pair.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "planningFallbackModelId",
|
id: "planningFallbackModelId",
|
||||||
name: "Planning fallback model",
|
name: "Planning fallback model",
|
||||||
@@ -309,6 +338,12 @@ export const BUILTIN_MOVED_WORKFLOW_SETTINGS: WorkflowSettingDefinition[] = [
|
|||||||
type: "string",
|
type: "string",
|
||||||
description: "Provider for the validation phase. Empty falls through to the global lane.",
|
description: "Provider for the validation phase. Empty falls through to the global lane.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "validatorCredentialInstanceId",
|
||||||
|
name: "Validator credential instance",
|
||||||
|
type: "string",
|
||||||
|
description: "Optional credential instance for the validator model pair.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "validatorModelId",
|
id: "validatorModelId",
|
||||||
name: "Validator model",
|
name: "Validator model",
|
||||||
@@ -328,6 +363,12 @@ export const BUILTIN_MOVED_WORKFLOW_SETTINGS: WorkflowSettingDefinition[] = [
|
|||||||
type: "string",
|
type: "string",
|
||||||
description: "Fallback provider for the validation phase.",
|
description: "Fallback provider for the validation phase.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "validatorFallbackCredentialInstanceId",
|
||||||
|
name: "Validator fallback credential instance",
|
||||||
|
type: "string",
|
||||||
|
description: "Optional credential instance for the validator fallback model pair.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "validatorFallbackModelId",
|
id: "validatorFallbackModelId",
|
||||||
name: "Validator fallback model",
|
name: "Validator fallback model",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { routeModel } from "./model-router.js";
|
|||||||
export interface ResolvedModelSelection {
|
export interface ResolvedModelSelection {
|
||||||
provider?: string;
|
provider?: string;
|
||||||
modelId?: string;
|
modelId?: string;
|
||||||
|
credentialInstanceId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModelThinkingPhase = "execution" | "planning" | "validation";
|
export type ModelThinkingPhase = "execution" | "planning" | "validation";
|
||||||
@@ -32,6 +33,7 @@ type ModelPair =
|
|||||||
| {
|
| {
|
||||||
provider?: string | null;
|
provider?: string | null;
|
||||||
modelId?: string | null;
|
modelId?: string | null;
|
||||||
|
credentialInstanceId?: string | null;
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
@@ -44,16 +46,31 @@ type TaskModelLike = {
|
|||||||
planningModelId?: string | null;
|
planningModelId?: string | null;
|
||||||
mergerModelProvider?: string | null;
|
mergerModelProvider?: string | null;
|
||||||
mergerModelId?: string | null;
|
mergerModelId?: string | null;
|
||||||
|
credentialInstanceId?: string | null;
|
||||||
|
validatorCredentialInstanceId?: string | null;
|
||||||
|
planningCredentialInstanceId?: string | null;
|
||||||
|
mergerCredentialInstanceId?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
function hasCompleteModelPair(pair: ModelPair): pair is { provider: string; modelId: string } {
|
function hasCompleteModelPair(pair: ModelPair): pair is { provider: string; modelId: string; credentialInstanceId?: string | null } {
|
||||||
return Boolean(pair?.provider && pair?.modelId);
|
return Boolean(pair?.provider && pair?.modelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FNXC:CredentialInstanceSelection 2026-08-01-05:38:
|
||||||
|
* Credential instance selection is persisted but inert in this slice. A winning provider/model
|
||||||
|
* pair carries only its own optional instance id so existing resolution precedence is unchanged.
|
||||||
|
*/
|
||||||
function pickFirstModelPair(...pairs: ModelPair[]): ResolvedModelSelection {
|
function pickFirstModelPair(...pairs: ModelPair[]): ResolvedModelSelection {
|
||||||
for (const pair of pairs) {
|
for (const pair of pairs) {
|
||||||
if (hasCompleteModelPair(pair)) {
|
if (hasCompleteModelPair(pair)) {
|
||||||
return { provider: pair.provider, modelId: pair.modelId };
|
return {
|
||||||
|
provider: pair.provider,
|
||||||
|
modelId: pair.modelId,
|
||||||
|
...(typeof pair.credentialInstanceId === "string" && pair.credentialInstanceId.length > 0
|
||||||
|
? { credentialInstanceId: pair.credentialInstanceId }
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
@@ -150,10 +167,12 @@ export function resolveProjectDefaultModel(settings?: Partial<Settings>): Resolv
|
|||||||
pickFirstModelPair(
|
pickFirstModelPair(
|
||||||
{
|
{
|
||||||
provider: settings?.defaultProviderOverride,
|
provider: settings?.defaultProviderOverride,
|
||||||
|
credentialInstanceId: settings?.defaultCredentialInstanceIdOverride,
|
||||||
modelId: settings?.defaultModelIdOverride,
|
modelId: settings?.defaultModelIdOverride,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: settings?.defaultProvider,
|
provider: settings?.defaultProvider,
|
||||||
|
credentialInstanceId: settings?.defaultCredentialInstanceId,
|
||||||
modelId: settings?.defaultModelId,
|
modelId: settings?.defaultModelId,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -166,14 +185,17 @@ export function resolveExecutionSettingsModel(settings?: Partial<Settings>): Res
|
|||||||
pickFirstModelPair(
|
pickFirstModelPair(
|
||||||
{
|
{
|
||||||
provider: settings?.executionProvider,
|
provider: settings?.executionProvider,
|
||||||
|
credentialInstanceId: settings?.executionCredentialInstanceId,
|
||||||
modelId: settings?.executionModelId,
|
modelId: settings?.executionModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: settings?.executionGlobalProvider,
|
provider: settings?.executionGlobalProvider,
|
||||||
|
credentialInstanceId: settings?.executionGlobalCredentialInstanceId,
|
||||||
modelId: settings?.executionGlobalModelId,
|
modelId: settings?.executionGlobalModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: resolveSelectedWorkflowModelLane(settings, "executionProvider"),
|
provider: resolveSelectedWorkflowModelLane(settings, "executionProvider"),
|
||||||
|
credentialInstanceId: resolveSelectedWorkflowModelLane(settings, "executionCredentialInstanceId"),
|
||||||
modelId: resolveSelectedWorkflowModelLane(settings, "executionModelId"),
|
modelId: resolveSelectedWorkflowModelLane(settings, "executionModelId"),
|
||||||
},
|
},
|
||||||
resolveProjectDefaultModel(settings),
|
resolveProjectDefaultModel(settings),
|
||||||
@@ -187,14 +209,17 @@ export function resolvePlanningSettingsModel(settings?: Partial<Settings>): Reso
|
|||||||
pickFirstModelPair(
|
pickFirstModelPair(
|
||||||
{
|
{
|
||||||
provider: settings?.planningProvider,
|
provider: settings?.planningProvider,
|
||||||
|
credentialInstanceId: settings?.planningCredentialInstanceId,
|
||||||
modelId: settings?.planningModelId,
|
modelId: settings?.planningModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: settings?.planningGlobalProvider,
|
provider: settings?.planningGlobalProvider,
|
||||||
|
credentialInstanceId: settings?.planningGlobalCredentialInstanceId,
|
||||||
modelId: settings?.planningGlobalModelId,
|
modelId: settings?.planningGlobalModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: resolveSelectedWorkflowModelLane(settings, "planningProvider"),
|
provider: resolveSelectedWorkflowModelLane(settings, "planningProvider"),
|
||||||
|
credentialInstanceId: resolveSelectedWorkflowModelLane(settings, "planningCredentialInstanceId"),
|
||||||
modelId: resolveSelectedWorkflowModelLane(settings, "planningModelId"),
|
modelId: resolveSelectedWorkflowModelLane(settings, "planningModelId"),
|
||||||
},
|
},
|
||||||
resolveProjectDefaultModel(settings),
|
resolveProjectDefaultModel(settings),
|
||||||
@@ -208,14 +233,17 @@ export function resolveValidatorSettingsModel(settings?: Partial<Settings>): Res
|
|||||||
pickFirstModelPair(
|
pickFirstModelPair(
|
||||||
{
|
{
|
||||||
provider: settings?.validatorProvider,
|
provider: settings?.validatorProvider,
|
||||||
|
credentialInstanceId: settings?.validatorCredentialInstanceId,
|
||||||
modelId: settings?.validatorModelId,
|
modelId: settings?.validatorModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: settings?.validatorGlobalProvider,
|
provider: settings?.validatorGlobalProvider,
|
||||||
|
credentialInstanceId: settings?.validatorGlobalCredentialInstanceId,
|
||||||
modelId: settings?.validatorGlobalModelId,
|
modelId: settings?.validatorGlobalModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: resolveSelectedWorkflowModelLane(settings, "validatorProvider"),
|
provider: resolveSelectedWorkflowModelLane(settings, "validatorProvider"),
|
||||||
|
credentialInstanceId: resolveSelectedWorkflowModelLane(settings, "validatorCredentialInstanceId"),
|
||||||
modelId: resolveSelectedWorkflowModelLane(settings, "validatorModelId"),
|
modelId: resolveSelectedWorkflowModelLane(settings, "validatorModelId"),
|
||||||
},
|
},
|
||||||
resolveProjectDefaultModel(settings),
|
resolveProjectDefaultModel(settings),
|
||||||
@@ -229,14 +257,17 @@ export function resolveTitleSummarizerSettingsModel(settings?: Partial<Settings>
|
|||||||
pickFirstModelPair(
|
pickFirstModelPair(
|
||||||
{
|
{
|
||||||
provider: settings?.titleSummarizerProvider,
|
provider: settings?.titleSummarizerProvider,
|
||||||
|
credentialInstanceId: settings?.titleSummarizerCredentialInstanceId,
|
||||||
modelId: settings?.titleSummarizerModelId,
|
modelId: settings?.titleSummarizerModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: settings?.titleSummarizerGlobalProvider,
|
provider: settings?.titleSummarizerGlobalProvider,
|
||||||
|
credentialInstanceId: settings?.titleSummarizerGlobalCredentialInstanceId,
|
||||||
modelId: settings?.titleSummarizerGlobalModelId,
|
modelId: settings?.titleSummarizerGlobalModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: settings?.planningProvider,
|
provider: settings?.planningProvider,
|
||||||
|
credentialInstanceId: settings?.planningCredentialInstanceId,
|
||||||
modelId: settings?.planningModelId,
|
modelId: settings?.planningModelId,
|
||||||
},
|
},
|
||||||
resolveProjectDefaultModel(settings),
|
resolveProjectDefaultModel(settings),
|
||||||
@@ -256,18 +287,22 @@ export function resolveImportTranslateSettingsModel(settings?: Partial<Settings>
|
|||||||
pickFirstModelPair(
|
pickFirstModelPair(
|
||||||
{
|
{
|
||||||
provider: settings?.importTranslateProvider,
|
provider: settings?.importTranslateProvider,
|
||||||
|
credentialInstanceId: settings?.importTranslateCredentialInstanceId,
|
||||||
modelId: settings?.importTranslateModelId,
|
modelId: settings?.importTranslateModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: settings?.importTranslateGlobalProvider,
|
provider: settings?.importTranslateGlobalProvider,
|
||||||
|
credentialInstanceId: settings?.importTranslateGlobalCredentialInstanceId,
|
||||||
modelId: settings?.importTranslateGlobalModelId,
|
modelId: settings?.importTranslateGlobalModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: settings?.titleSummarizerProvider,
|
provider: settings?.titleSummarizerProvider,
|
||||||
|
credentialInstanceId: settings?.titleSummarizerCredentialInstanceId,
|
||||||
modelId: settings?.titleSummarizerModelId,
|
modelId: settings?.titleSummarizerModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: settings?.titleSummarizerGlobalProvider,
|
provider: settings?.titleSummarizerGlobalProvider,
|
||||||
|
credentialInstanceId: settings?.titleSummarizerGlobalCredentialInstanceId,
|
||||||
modelId: settings?.titleSummarizerGlobalModelId,
|
modelId: settings?.titleSummarizerGlobalModelId,
|
||||||
},
|
},
|
||||||
resolveProjectDefaultModel(settings),
|
resolveProjectDefaultModel(settings),
|
||||||
@@ -287,10 +322,12 @@ export function resolveMergerSettingsModel(settings?: Partial<Settings>): Resolv
|
|||||||
pickFirstModelPair(
|
pickFirstModelPair(
|
||||||
{
|
{
|
||||||
provider: settings?.mergerProvider,
|
provider: settings?.mergerProvider,
|
||||||
|
credentialInstanceId: settings?.mergerCredentialInstanceId,
|
||||||
modelId: settings?.mergerModelId,
|
modelId: settings?.mergerModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: settings?.mergerGlobalProvider,
|
provider: settings?.mergerGlobalProvider,
|
||||||
|
credentialInstanceId: settings?.mergerGlobalCredentialInstanceId,
|
||||||
modelId: settings?.mergerGlobalModelId,
|
modelId: settings?.mergerGlobalModelId,
|
||||||
},
|
},
|
||||||
resolveProjectDefaultModel(settings),
|
resolveProjectDefaultModel(settings),
|
||||||
@@ -310,10 +347,12 @@ export function resolveMergerFallbackModel(settings?: Partial<Settings>): Resolv
|
|||||||
pickFirstModelPair(
|
pickFirstModelPair(
|
||||||
{
|
{
|
||||||
provider: settings?.mergerFallbackProvider,
|
provider: settings?.mergerFallbackProvider,
|
||||||
|
credentialInstanceId: settings?.mergerFallbackCredentialInstanceId,
|
||||||
modelId: settings?.mergerFallbackModelId,
|
modelId: settings?.mergerFallbackModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: settings?.fallbackProvider,
|
provider: settings?.fallbackProvider,
|
||||||
|
credentialInstanceId: settings?.fallbackCredentialInstanceId,
|
||||||
modelId: settings?.fallbackModelId,
|
modelId: settings?.fallbackModelId,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -331,14 +370,17 @@ export function resolveExecutorFallbackModel(settings?: Partial<Settings>): Reso
|
|||||||
pickFirstModelPair(
|
pickFirstModelPair(
|
||||||
{
|
{
|
||||||
provider: settings?.executionFallbackProvider,
|
provider: settings?.executionFallbackProvider,
|
||||||
|
credentialInstanceId: settings?.executionFallbackCredentialInstanceId,
|
||||||
modelId: settings?.executionFallbackModelId,
|
modelId: settings?.executionFallbackModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: settings?.fallbackProvider,
|
provider: settings?.fallbackProvider,
|
||||||
|
credentialInstanceId: settings?.fallbackCredentialInstanceId,
|
||||||
modelId: settings?.fallbackModelId,
|
modelId: settings?.fallbackModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: resolveSelectedWorkflowModelLane(settings, "executionFallbackProvider"),
|
provider: resolveSelectedWorkflowModelLane(settings, "executionFallbackProvider"),
|
||||||
|
credentialInstanceId: resolveSelectedWorkflowModelLane(settings, "executionFallbackCredentialInstanceId"),
|
||||||
modelId: resolveSelectedWorkflowModelLane(settings, "executionFallbackModelId"),
|
modelId: resolveSelectedWorkflowModelLane(settings, "executionFallbackModelId"),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -351,14 +393,17 @@ export function resolvePlanningFallbackModel(settings?: Partial<Settings>): Reso
|
|||||||
pickFirstModelPair(
|
pickFirstModelPair(
|
||||||
{
|
{
|
||||||
provider: settings?.planningFallbackProvider,
|
provider: settings?.planningFallbackProvider,
|
||||||
|
credentialInstanceId: settings?.planningFallbackCredentialInstanceId,
|
||||||
modelId: settings?.planningFallbackModelId,
|
modelId: settings?.planningFallbackModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: settings?.fallbackProvider,
|
provider: settings?.fallbackProvider,
|
||||||
|
credentialInstanceId: settings?.fallbackCredentialInstanceId,
|
||||||
modelId: settings?.fallbackModelId,
|
modelId: settings?.fallbackModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: resolveSelectedWorkflowModelLane(settings, "planningFallbackProvider"),
|
provider: resolveSelectedWorkflowModelLane(settings, "planningFallbackProvider"),
|
||||||
|
credentialInstanceId: resolveSelectedWorkflowModelLane(settings, "planningFallbackCredentialInstanceId"),
|
||||||
modelId: resolveSelectedWorkflowModelLane(settings, "planningFallbackModelId"),
|
modelId: resolveSelectedWorkflowModelLane(settings, "planningFallbackModelId"),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -371,14 +416,17 @@ export function resolveValidatorFallbackModel(settings?: Partial<Settings>): Res
|
|||||||
pickFirstModelPair(
|
pickFirstModelPair(
|
||||||
{
|
{
|
||||||
provider: settings?.validatorFallbackProvider,
|
provider: settings?.validatorFallbackProvider,
|
||||||
|
credentialInstanceId: settings?.validatorFallbackCredentialInstanceId,
|
||||||
modelId: settings?.validatorFallbackModelId,
|
modelId: settings?.validatorFallbackModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: settings?.fallbackProvider,
|
provider: settings?.fallbackProvider,
|
||||||
|
credentialInstanceId: settings?.fallbackCredentialInstanceId,
|
||||||
modelId: settings?.fallbackModelId,
|
modelId: settings?.fallbackModelId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: resolveSelectedWorkflowModelLane(settings, "validatorFallbackProvider"),
|
provider: resolveSelectedWorkflowModelLane(settings, "validatorFallbackProvider"),
|
||||||
|
credentialInstanceId: resolveSelectedWorkflowModelLane(settings, "validatorFallbackCredentialInstanceId"),
|
||||||
modelId: resolveSelectedWorkflowModelLane(settings, "validatorFallbackModelId"),
|
modelId: resolveSelectedWorkflowModelLane(settings, "validatorFallbackModelId"),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -394,6 +442,7 @@ export function resolveTaskExecutionModel(
|
|||||||
pickFirstModelPair(
|
pickFirstModelPair(
|
||||||
{
|
{
|
||||||
provider: task.modelProvider,
|
provider: task.modelProvider,
|
||||||
|
credentialInstanceId: task.credentialInstanceId,
|
||||||
modelId: task.modelId,
|
modelId: task.modelId,
|
||||||
},
|
},
|
||||||
resolveExecutionSettingsModel(settings),
|
resolveExecutionSettingsModel(settings),
|
||||||
@@ -410,6 +459,7 @@ export function resolveTaskValidatorModel(
|
|||||||
pickFirstModelPair(
|
pickFirstModelPair(
|
||||||
{
|
{
|
||||||
provider: task.validatorModelProvider,
|
provider: task.validatorModelProvider,
|
||||||
|
credentialInstanceId: task.validatorCredentialInstanceId,
|
||||||
modelId: task.validatorModelId,
|
modelId: task.validatorModelId,
|
||||||
},
|
},
|
||||||
resolveValidatorSettingsModel(settings),
|
resolveValidatorSettingsModel(settings),
|
||||||
@@ -426,6 +476,7 @@ export function resolveTaskPlanningModel(
|
|||||||
pickFirstModelPair(
|
pickFirstModelPair(
|
||||||
{
|
{
|
||||||
provider: task.planningModelProvider,
|
provider: task.planningModelProvider,
|
||||||
|
credentialInstanceId: task.planningCredentialInstanceId,
|
||||||
modelId: task.planningModelId,
|
modelId: task.planningModelId,
|
||||||
},
|
},
|
||||||
resolvePlanningSettingsModel(settings),
|
resolvePlanningSettingsModel(settings),
|
||||||
@@ -445,7 +496,7 @@ export function resolveTaskMergerModel(
|
|||||||
): ResolvedModelSelection {
|
): ResolvedModelSelection {
|
||||||
return applyTestModeOverrides(
|
return applyTestModeOverrides(
|
||||||
pickFirstModelPair(
|
pickFirstModelPair(
|
||||||
{ provider: task.mergerModelProvider, modelId: task.mergerModelId },
|
{ provider: task.mergerModelProvider, modelId: task.mergerModelId, credentialInstanceId: task.mergerCredentialInstanceId },
|
||||||
resolveMergerSettingsModel(settings),
|
resolveMergerSettingsModel(settings),
|
||||||
),
|
),
|
||||||
settings,
|
settings,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
FNXC:CredentialInstanceSelection 2026-08-01-05:53:
|
||||||
|
Task credential-instance companions are persisted-but-inert in this slice. The project-partitioned
|
||||||
|
`tasks` table owns model overrides, so upgrades must add these nullable columns in the same schema.
|
||||||
|
*/
|
||||||
|
ALTER TABLE project.tasks ADD COLUMN IF NOT EXISTS credential_instance_id text;
|
||||||
|
ALTER TABLE project.tasks ADD COLUMN IF NOT EXISTS validator_credential_instance_id text;
|
||||||
|
ALTER TABLE project.tasks ADD COLUMN IF NOT EXISTS planning_credential_instance_id text;
|
||||||
|
ALTER TABLE project.tasks ADD COLUMN IF NOT EXISTS merger_credential_instance_id text;
|
||||||
@@ -54,7 +54,7 @@ FNXC:MissionTaskPrefix 2026-07-30-21:10 (rebase onto migrated main):
|
|||||||
SCHEMA_BASELINE_VERSION advances to 0038 for optional per-mission task_prefix — 0037 is the
|
SCHEMA_BASELINE_VERSION advances to 0038 for optional per-mission task_prefix — 0037 is the
|
||||||
capacity-model table drop that landed while this PR was open.
|
capacity-model table drop that landed while this PR was open.
|
||||||
*/
|
*/
|
||||||
export const SCHEMA_BASELINE_VERSION = "0038";
|
export const SCHEMA_BASELINE_VERSION = "0039";
|
||||||
/** FNXC:SymbolLock 2026-07-20-10:00: upgrades need durable task declarations before admission resolves symbols. */
|
/** FNXC:SymbolLock 2026-07-20-10:00: upgrades need durable task declarations before admission resolves symbols. */
|
||||||
export const TASK_DECLARED_SYMBOLS_VERSION = "0028";
|
export const TASK_DECLARED_SYMBOLS_VERSION = "0028";
|
||||||
const INITIAL_SCHEMA_VERSION = "0000";
|
const INITIAL_SCHEMA_VERSION = "0000";
|
||||||
@@ -173,6 +173,8 @@ second would read as already-applied and silently never run. Renumbered rather t
|
|||||||
is landed on real databases and its identity is immutable, per the MONITOR_APPROVAL note above.
|
is landed on real databases and its identity is immutable, per the MONITOR_APPROVAL note above.
|
||||||
*/
|
*/
|
||||||
export const MISSION_TASK_PREFIX_VERSION = "0038";
|
export const MISSION_TASK_PREFIX_VERSION = "0038";
|
||||||
|
/** FNXC:CredentialInstanceSelection 2026-08-01-05:43: explicit registration prevents migration 0039 from being silently skipped on upgraded PostgreSQL databases. */
|
||||||
|
export const CREDENTIAL_INSTANCE_SELECTION_VERSION = "0039";
|
||||||
|
|
||||||
/** SECURITY DEFINER helper that only inserts LEGACY_ADOPTION_DRAINED_MARKER. */
|
/** SECURITY DEFINER helper that only inserts LEGACY_ADOPTION_DRAINED_MARKER. */
|
||||||
export const LEGACY_ADOPTION_DRAINED_MARKER_FUNCTION = "fusion_mark_legacy_adoption_drained";
|
export const LEGACY_ADOPTION_DRAINED_MARKER_FUNCTION = "fusion_mark_legacy_adoption_drained";
|
||||||
@@ -385,6 +387,7 @@ const MISSION_LINEAGE_STOP_MIGRATION_PATH = join(MIGRATIONS_DIR, "0035_fn_8543_m
|
|||||||
const CHAT_SESSION_TAGS_MIGRATION_PATH = join(MIGRATIONS_DIR, "0036_chat_session_tags.sql");
|
const CHAT_SESSION_TAGS_MIGRATION_PATH = join(MIGRATIONS_DIR, "0036_chat_session_tags.sql");
|
||||||
const DROP_GLOBAL_CONCURRENCY_MIGRATION_PATH = join(MIGRATIONS_DIR, "0037_drop_global_concurrency.sql");
|
const DROP_GLOBAL_CONCURRENCY_MIGRATION_PATH = join(MIGRATIONS_DIR, "0037_drop_global_concurrency.sql");
|
||||||
const MISSION_TASK_PREFIX_MIGRATION_PATH = join(MIGRATIONS_DIR, "0038_mission_task_prefix.sql");
|
const MISSION_TASK_PREFIX_MIGRATION_PATH = join(MIGRATIONS_DIR, "0038_mission_task_prefix.sql");
|
||||||
|
const CREDENTIAL_INSTANCE_SELECTION_MIGRATION_PATH = join(MIGRATIONS_DIR, "0039_fn_8660_credential_instance_selection.sql");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure the migration bookkeeping table exists. Lives in the public schema so
|
* Ensure the migration bookkeeping table exists. Lives in the public schema so
|
||||||
@@ -493,6 +496,7 @@ export async function applySchemaBaseline(
|
|||||||
const chatSessionTagsAlreadyApplied = applied.includes(CHAT_SESSION_TAGS_VERSION);
|
const chatSessionTagsAlreadyApplied = applied.includes(CHAT_SESSION_TAGS_VERSION);
|
||||||
const dropGlobalConcurrencyAlreadyApplied = applied.includes(DROP_GLOBAL_CONCURRENCY_VERSION);
|
const dropGlobalConcurrencyAlreadyApplied = applied.includes(DROP_GLOBAL_CONCURRENCY_VERSION);
|
||||||
const missionTaskPrefixAlreadyApplied = applied.includes(MISSION_TASK_PREFIX_VERSION);
|
const missionTaskPrefixAlreadyApplied = applied.includes(MISSION_TASK_PREFIX_VERSION);
|
||||||
|
const credentialInstanceSelectionAlreadyApplied = applied.includes(CREDENTIAL_INSTANCE_SELECTION_VERSION);
|
||||||
assertBinaryNotOlderThanDatabase(applied);
|
assertBinaryNotOlderThanDatabase(applied);
|
||||||
let schemaChanged = false;
|
let schemaChanged = false;
|
||||||
|
|
||||||
@@ -1042,6 +1046,14 @@ export async function applySchemaBaseline(
|
|||||||
schemaChanged = true;
|
schemaChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* FNXC:CredentialInstanceSelection 2026-08-01-05:43: upgraded task rows need nullable persisted instance companions before model-selection writers can store them; this is data-only and does not resolve credentials at runtime. */
|
||||||
|
if (!credentialInstanceSelectionAlreadyApplied) {
|
||||||
|
const migrationSql = await readFile(CREDENTIAL_INSTANCE_SELECTION_MIGRATION_PATH, "utf8");
|
||||||
|
await tx.execute(sql.raw(migrationSql));
|
||||||
|
await tx.execute(sql`INSERT INTO public.${sql.identifier(MIGRATION_BOOKKEEPING_TABLE)} (version) VALUES (${CREDENTIAL_INSTANCE_SELECTION_VERSION}) ON CONFLICT (version) DO NOTHING`);
|
||||||
|
schemaChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
return { applied: schemaChanged, pluginHooksRun: pluginHooks.length };
|
return { applied: schemaChanged, pluginHooksRun: pluginHooks.length };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,12 +92,16 @@ export const tasks = projectSchema.table("tasks", {
|
|||||||
baseCommitSha: text("base_commit_sha"),
|
baseCommitSha: text("base_commit_sha"),
|
||||||
modelPresetId: text("model_preset_id"),
|
modelPresetId: text("model_preset_id"),
|
||||||
modelProvider: text("model_provider"),
|
modelProvider: text("model_provider"),
|
||||||
|
credentialInstanceId: text("credential_instance_id"),
|
||||||
modelId: text("model_id"),
|
modelId: text("model_id"),
|
||||||
validatorModelProvider: text("validator_model_provider"),
|
validatorModelProvider: text("validator_model_provider"),
|
||||||
|
validatorCredentialInstanceId: text("validator_credential_instance_id"),
|
||||||
validatorModelId: text("validator_model_id"),
|
validatorModelId: text("validator_model_id"),
|
||||||
planningModelProvider: text("planning_model_provider"),
|
planningModelProvider: text("planning_model_provider"),
|
||||||
|
planningCredentialInstanceId: text("planning_credential_instance_id"),
|
||||||
planningModelId: text("planning_model_id"),
|
planningModelId: text("planning_model_id"),
|
||||||
mergerModelProvider: text("merger_model_provider"),
|
mergerModelProvider: text("merger_model_provider"),
|
||||||
|
mergerCredentialInstanceId: text("merger_credential_instance_id"),
|
||||||
mergerModelId: text("merger_model_id"),
|
mergerModelId: text("merger_model_id"),
|
||||||
mergerThinkingLevel: text("merger_thinking_level"),
|
mergerThinkingLevel: text("merger_thinking_level"),
|
||||||
mergeRetries: integer("merge_retries"),
|
mergeRetries: integer("merge_retries"),
|
||||||
|
|||||||
@@ -41,21 +41,27 @@ type MovedProjectSettingsKey =
|
|||||||
| "maxReviewerFallbackRetries"
|
| "maxReviewerFallbackRetries"
|
||||||
| "reflectionEnabled"
|
| "reflectionEnabled"
|
||||||
| "executionProvider"
|
| "executionProvider"
|
||||||
|
| "executionCredentialInstanceId"
|
||||||
| "executionModelId"
|
| "executionModelId"
|
||||||
| "executionThinkingLevel"
|
| "executionThinkingLevel"
|
||||||
| "executionFallbackProvider"
|
| "executionFallbackProvider"
|
||||||
|
| "executionFallbackCredentialInstanceId"
|
||||||
| "executionFallbackModelId"
|
| "executionFallbackModelId"
|
||||||
| "executionFallbackThinkingLevel"
|
| "executionFallbackThinkingLevel"
|
||||||
| "planningProvider"
|
| "planningProvider"
|
||||||
|
| "planningCredentialInstanceId"
|
||||||
| "planningModelId"
|
| "planningModelId"
|
||||||
| "planningThinkingLevel"
|
| "planningThinkingLevel"
|
||||||
| "planningFallbackProvider"
|
| "planningFallbackProvider"
|
||||||
|
| "planningFallbackCredentialInstanceId"
|
||||||
| "planningFallbackModelId"
|
| "planningFallbackModelId"
|
||||||
| "planningFallbackThinkingLevel"
|
| "planningFallbackThinkingLevel"
|
||||||
| "validatorProvider"
|
| "validatorProvider"
|
||||||
|
| "validatorCredentialInstanceId"
|
||||||
| "validatorModelId"
|
| "validatorModelId"
|
||||||
| "validatorThinkingLevel"
|
| "validatorThinkingLevel"
|
||||||
| "validatorFallbackProvider"
|
| "validatorFallbackProvider"
|
||||||
|
| "validatorFallbackCredentialInstanceId"
|
||||||
| "validatorFallbackModelId"
|
| "validatorFallbackModelId"
|
||||||
| "validatorFallbackThinkingLevel";
|
| "validatorFallbackThinkingLevel";
|
||||||
|
|
||||||
@@ -141,6 +147,7 @@ export const DEFAULT_GLOBAL_SETTINGS = {
|
|||||||
skipConfirmationDialogs: false,
|
skipConfirmationDialogs: false,
|
||||||
language: undefined,
|
language: undefined,
|
||||||
defaultProvider: undefined,
|
defaultProvider: undefined,
|
||||||
|
defaultCredentialInstanceId: undefined,
|
||||||
defaultModelId: undefined,
|
defaultModelId: undefined,
|
||||||
testMode: undefined,
|
testMode: undefined,
|
||||||
voiceInput: undefined,
|
voiceInput: undefined,
|
||||||
@@ -152,6 +159,7 @@ export const DEFAULT_GLOBAL_SETTINGS = {
|
|||||||
modelRouterCheapModelId: undefined,
|
modelRouterCheapModelId: undefined,
|
||||||
mergeRequestContractShadowEnabled: false,
|
mergeRequestContractShadowEnabled: false,
|
||||||
fallbackProvider: undefined,
|
fallbackProvider: undefined,
|
||||||
|
fallbackCredentialInstanceId: undefined,
|
||||||
fallbackModelId: undefined,
|
fallbackModelId: undefined,
|
||||||
/*
|
/*
|
||||||
FNXC:Settings-ThinkingLevel 2026-07-10-11:13:
|
FNXC:Settings-ThinkingLevel 2026-07-10-11:13:
|
||||||
@@ -248,24 +256,30 @@ export const DEFAULT_GLOBAL_SETTINGS = {
|
|||||||
ompCliBinaryPath: undefined,
|
ompCliBinaryPath: undefined,
|
||||||
// Global baseline lanes for per-role model selection
|
// Global baseline lanes for per-role model selection
|
||||||
executionGlobalProvider: undefined,
|
executionGlobalProvider: undefined,
|
||||||
|
executionGlobalCredentialInstanceId: undefined,
|
||||||
executionGlobalModelId: undefined,
|
executionGlobalModelId: undefined,
|
||||||
planningGlobalProvider: undefined,
|
planningGlobalProvider: undefined,
|
||||||
|
planningGlobalCredentialInstanceId: undefined,
|
||||||
planningGlobalModelId: undefined,
|
planningGlobalModelId: undefined,
|
||||||
validatorGlobalProvider: undefined,
|
validatorGlobalProvider: undefined,
|
||||||
|
validatorGlobalCredentialInstanceId: undefined,
|
||||||
validatorGlobalModelId: undefined,
|
validatorGlobalModelId: undefined,
|
||||||
titleSummarizerGlobalProvider: undefined,
|
titleSummarizerGlobalProvider: undefined,
|
||||||
|
titleSummarizerGlobalCredentialInstanceId: undefined,
|
||||||
titleSummarizerGlobalModelId: undefined,
|
titleSummarizerGlobalModelId: undefined,
|
||||||
/*
|
/*
|
||||||
FNXC:Settings-MergerModel 2026-07-13-07:52:
|
FNXC:Settings-MergerModel 2026-07-13-07:52:
|
||||||
Global merger baseline lane (provider/model/thinking) is independent of executor/planner/reviewer so operators can pin a merge-capable model under Settings → Global Models without changing other lanes. Undefined falls through to defaultProvider/defaultModelId at resolve time.
|
Global merger baseline lane (provider/model/thinking) is independent of executor/planner/reviewer so operators can pin a merge-capable model under Settings → Global Models without changing other lanes. Undefined falls through to defaultProvider/defaultModelId at resolve time.
|
||||||
*/
|
*/
|
||||||
mergerGlobalProvider: undefined,
|
mergerGlobalProvider: undefined,
|
||||||
|
mergerGlobalCredentialInstanceId: undefined,
|
||||||
mergerGlobalModelId: undefined,
|
mergerGlobalModelId: undefined,
|
||||||
/*
|
/*
|
||||||
FNXC:GitHubImportTranslate 2026-07-15-09:30:
|
FNXC:GitHubImportTranslate 2026-07-15-09:30:
|
||||||
Global import-translate baseline lane. Undefined falls through to the summarization lane then defaultProvider/defaultModelId at resolve time.
|
Global import-translate baseline lane. Undefined falls through to the summarization lane then defaultProvider/defaultModelId at resolve time.
|
||||||
*/
|
*/
|
||||||
importTranslateGlobalProvider: undefined,
|
importTranslateGlobalProvider: undefined,
|
||||||
|
importTranslateGlobalCredentialInstanceId: undefined,
|
||||||
importTranslateGlobalModelId: undefined,
|
importTranslateGlobalModelId: undefined,
|
||||||
importTranslateGlobalThinkingLevel: undefined,
|
importTranslateGlobalThinkingLevel: undefined,
|
||||||
/*
|
/*
|
||||||
@@ -536,6 +550,7 @@ export const DEFAULT_PROJECT_SETTINGS = {
|
|||||||
// (executionGlobalProvider etc.) stay global; project default overrides stay.
|
// (executionGlobalProvider etc.) stay global; project default overrides stay.
|
||||||
// Project-level default override (NOT moved — stays project-scoped)
|
// Project-level default override (NOT moved — stays project-scoped)
|
||||||
defaultProviderOverride: undefined,
|
defaultProviderOverride: undefined,
|
||||||
|
defaultCredentialInstanceIdOverride: undefined,
|
||||||
defaultModelIdOverride: undefined,
|
defaultModelIdOverride: undefined,
|
||||||
/*
|
/*
|
||||||
FNXC:Settings-ThinkingLevel 2026-07-10-00:00:
|
FNXC:Settings-ThinkingLevel 2026-07-10-00:00:
|
||||||
@@ -716,9 +731,11 @@ export const DEFAULT_PROJECT_SETTINGS = {
|
|||||||
useAiMergeCommitSummary: true,
|
useAiMergeCommitSummary: true,
|
||||||
// Title-summarizer model lanes stay project-scoped (not moved in U4).
|
// Title-summarizer model lanes stay project-scoped (not moved in U4).
|
||||||
titleSummarizerProvider: undefined,
|
titleSummarizerProvider: undefined,
|
||||||
|
titleSummarizerCredentialInstanceId: undefined,
|
||||||
titleSummarizerModelId: undefined,
|
titleSummarizerModelId: undefined,
|
||||||
titleSummarizerThinkingLevel: undefined,
|
titleSummarizerThinkingLevel: undefined,
|
||||||
titleSummarizerFallbackProvider: undefined,
|
titleSummarizerFallbackProvider: undefined,
|
||||||
|
titleSummarizerFallbackCredentialInstanceId: undefined,
|
||||||
titleSummarizerFallbackModelId: undefined,
|
titleSummarizerFallbackModelId: undefined,
|
||||||
titleSummarizerFallbackThinkingLevel: undefined,
|
titleSummarizerFallbackThinkingLevel: undefined,
|
||||||
/*
|
/*
|
||||||
@@ -728,6 +745,7 @@ export const DEFAULT_PROJECT_SETTINGS = {
|
|||||||
githubImportAutoTranslate: false,
|
githubImportAutoTranslate: false,
|
||||||
importTranslateTargetLocale: undefined,
|
importTranslateTargetLocale: undefined,
|
||||||
importTranslateProvider: undefined,
|
importTranslateProvider: undefined,
|
||||||
|
importTranslateCredentialInstanceId: undefined,
|
||||||
importTranslateModelId: undefined,
|
importTranslateModelId: undefined,
|
||||||
importTranslateThinkingLevel: undefined,
|
importTranslateThinkingLevel: undefined,
|
||||||
/*
|
/*
|
||||||
@@ -735,10 +753,12 @@ export const DEFAULT_PROJECT_SETTINGS = {
|
|||||||
Merger model lane stays project-scoped (not workflow-moved) like title summarizer: Settings → Project Models can override the global merger baseline without binding the choice to a workflow graph.
|
Merger model lane stays project-scoped (not workflow-moved) like title summarizer: Settings → Project Models can override the global merger baseline without binding the choice to a workflow graph.
|
||||||
*/
|
*/
|
||||||
mergerProvider: undefined,
|
mergerProvider: undefined,
|
||||||
|
mergerCredentialInstanceId: undefined,
|
||||||
mergerModelId: undefined,
|
mergerModelId: undefined,
|
||||||
mergerThinkingLevel: undefined,
|
mergerThinkingLevel: undefined,
|
||||||
// FNXC:Settings-MergerModel 2026-07-16-00:00: project merger fallback overrides shared global fallback only when its provider/model pair is complete.
|
// FNXC:Settings-MergerModel 2026-07-16-00:00: project merger fallback overrides shared global fallback only when its provider/model pair is complete.
|
||||||
mergerFallbackProvider: undefined,
|
mergerFallbackProvider: undefined,
|
||||||
|
mergerFallbackCredentialInstanceId: undefined,
|
||||||
mergerFallbackModelId: undefined,
|
mergerFallbackModelId: undefined,
|
||||||
mergerFallbackThinkingLevel: undefined,
|
mergerFallbackThinkingLevel: undefined,
|
||||||
prTitlePromptInstructions: undefined,
|
prTitlePromptInstructions: undefined,
|
||||||
@@ -858,6 +878,11 @@ export const DEFAULT_PROJECT_SETTINGS = {
|
|||||||
* that matches the legacy `DEFAULT_SETTINGS` shape.
|
* that matches the legacy `DEFAULT_SETTINGS` shape.
|
||||||
*/
|
*/
|
||||||
export const DEFAULT_SETTINGS: Settings = {
|
export const DEFAULT_SETTINGS: Settings = {
|
||||||
|
/*
|
||||||
|
* FNXC:CredentialInstanceSelection 2026-08-01-05:38:
|
||||||
|
* Optional credential-instance settings share each provider lane's scope and remain inert
|
||||||
|
* until runtime credential selection is introduced by the follow-up slice.
|
||||||
|
*/
|
||||||
...DEFAULT_GLOBAL_SETTINGS,
|
...DEFAULT_GLOBAL_SETTINGS,
|
||||||
...DEFAULT_PROJECT_SETTINGS,
|
...DEFAULT_PROJECT_SETTINGS,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1319,7 +1319,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
|||||||
}
|
}
|
||||||
async updateTask(
|
async updateTask(
|
||||||
id: string,
|
id: string,
|
||||||
updates: { title?: string; description?: string; priority?: TaskPriority | null; prompt?: string; worktree?: string | null; workspaceWorktrees?: import("./types.js").Task["workspaceWorktrees"]; status?: string | null; dependencies?: string[]; steps?: import("./types.js").TaskStep[]; customFields?: Record<string, unknown>; currentStep?: number; blockedBy?: string | null; overlapBlockedBy?: string | null; assignedAgentId?: string | null; pausedByAgentId?: string | null; pausedReason?: string | null; wedgeNotification?: import("./types.js").TaskWedgeNotificationState | null; tokenBudgetSoftAlertedAt?: string | null; worktrunkFallbackAlertedAt?: string | null; worktrunkFailure?: import("./types.js").Task["worktrunkFailure"] | null; tokenBudgetHardAlertedAt?: string | null; tokenBudgetOverride?: import("./types.js").TaskTokenBudgetOverride | null; dispatchStormCount?: number | null; lastDispatchAt?: string | null; assigneeUserId?: string | null; scopeOverride?: boolean | null; scopeOverrideReason?: string | null; scopeAutoWiden?: string[] | null; nodeId?: string | null; effectiveNodeId?: string | null; effectiveNodeSource?: string | null; checkedOutBy?: string | null; checkedOutAt?: string | null; checkoutNodeId?: string | null; checkoutRunId?: string | null; checkoutLeaseRenewedAt?: string | null; checkoutLeaseEpoch?: number | null; paused?: boolean; baseBranch?: string | null; autoMerge?: boolean | null; branch?: string | null; executionStartBranch?: string | null; baseCommitSha?: string | null; size?: "S" | "M" | "L"; reviewLevel?: number; executionMode?: import("./types.js").ExecutionMode | null; mergeRetries?: number; workflowStepRetries?: number; stuckKillCount?: number | null; resumeLimboCount?: number | null; executeRequeueLoopCount?: number | null; graphResumeRetryCount?: number | null; consecutiveToolFailureRetryCount?: number | null; executorEscalationAttempted?: boolean | null; toolFailureDetectorLogCursor?: number | null; toolFailureRetryExhaustedAuditEmitted?: boolean | null; resumeLimboTipSha?: string | null; resumeLimboStepSignature?: string | null; executeRequeueLoopSignature?: string | null; postReviewFixCount?: number | null; planReviewReplanCount?: number | null; recoveryRetryCount?: number | null; taskDoneRetryCount?: number | null; bulkCompletionRefusalAt?: string | null; workflowIrPin?: string | null; workflowIrPinNodeId?: string | null; workflowIrPinColumnId?: string | null; legacyAdoptedAt?: string | null; worktreeSessionRetryCount?: number | null; completionHandoffLimboRecoveryCount?: number | null; verificationFailureCount?: number | null; mergeConflictBounceCount?: number | null; mergeAuditBounceCount?: number | null; mergeTransientRetryCount?: number | null; branchConflictRecoveryCount?: number | null; reviewerContextRetryCount?: number | null; reviewerFallbackRetryCount?: number | null; nextRecoveryAt?: string | null; enabledWorkflowSteps?: string[]; noCommitsExpected?: boolean | null; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; planningModelProvider?: string | null; planningModelId?: string | null; mergerModelProvider?: string | null; mergerModelId?: string | null; thinkingLevel?: string | null; validatorThinkingLevel?: string | null; planningThinkingLevel?: string | null; mergerThinkingLevel?: string | null; error?: string | null; summary?: string | null; sessionFile?: string | null; firstExecutionAt?: string | null; cumulativeActiveMs?: number | null; cumulativePlanningMs?: number | null; planningStartedAt?: string | null; executionStartedAt?: string | null; executionCompletedAt?: string | null; review?: import("./types.js").TaskReview | null; reviewState?: import("./types.js").TaskReviewState | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null; mergeDetails?: import("./types.js").MergeDetails | null; sourceIssue?: import("./types.js").TaskSourceIssue | null; sourceMetadataPatch?: Record<string, unknown> | null; githubTracking?: import("./types.js").TaskGithubTracking | null; tokenUsage?: import("./types.js").TaskTokenUsage | null; modifiedFiles?: string[] | null; declaredSymbols?: string[] | null | undefined; missionId?: string | null; sliceId?: string | null; workflowTransitionNotification?: import("./types.js").WorkflowTransitionNotificationMarker | undefined; plannerOversightLevel?: string | null; sessionAdvisorEnabled?: boolean | null; approvedPlanFingerprint?: string | null }, runContext?: RunMutationContext,
|
updates: { title?: string; description?: string; priority?: TaskPriority | null; prompt?: string; worktree?: string | null; workspaceWorktrees?: import("./types.js").Task["workspaceWorktrees"]; status?: string | null; dependencies?: string[]; steps?: import("./types.js").TaskStep[]; customFields?: Record<string, unknown>; currentStep?: number; blockedBy?: string | null; overlapBlockedBy?: string | null; assignedAgentId?: string | null; pausedByAgentId?: string | null; pausedReason?: string | null; wedgeNotification?: import("./types.js").TaskWedgeNotificationState | null; tokenBudgetSoftAlertedAt?: string | null; worktrunkFallbackAlertedAt?: string | null; worktrunkFailure?: import("./types.js").Task["worktrunkFailure"] | null; tokenBudgetHardAlertedAt?: string | null; tokenBudgetOverride?: import("./types.js").TaskTokenBudgetOverride | null; dispatchStormCount?: number | null; lastDispatchAt?: string | null; assigneeUserId?: string | null; scopeOverride?: boolean | null; scopeOverrideReason?: string | null; scopeAutoWiden?: string[] | null; nodeId?: string | null; effectiveNodeId?: string | null; effectiveNodeSource?: string | null; checkedOutBy?: string | null; checkedOutAt?: string | null; checkoutNodeId?: string | null; checkoutRunId?: string | null; checkoutLeaseRenewedAt?: string | null; checkoutLeaseEpoch?: number | null; paused?: boolean; baseBranch?: string | null; autoMerge?: boolean | null; branch?: string | null; executionStartBranch?: string | null; baseCommitSha?: string | null; size?: "S" | "M" | "L"; reviewLevel?: number; executionMode?: import("./types.js").ExecutionMode | null; mergeRetries?: number; workflowStepRetries?: number; stuckKillCount?: number | null; resumeLimboCount?: number | null; executeRequeueLoopCount?: number | null; graphResumeRetryCount?: number | null; consecutiveToolFailureRetryCount?: number | null; executorEscalationAttempted?: boolean | null; toolFailureDetectorLogCursor?: number | null; toolFailureRetryExhaustedAuditEmitted?: boolean | null; resumeLimboTipSha?: string | null; resumeLimboStepSignature?: string | null; executeRequeueLoopSignature?: string | null; postReviewFixCount?: number | null; planReviewReplanCount?: number | null; recoveryRetryCount?: number | null; taskDoneRetryCount?: number | null; bulkCompletionRefusalAt?: string | null; workflowIrPin?: string | null; workflowIrPinNodeId?: string | null; workflowIrPinColumnId?: string | null; legacyAdoptedAt?: string | null; worktreeSessionRetryCount?: number | null; completionHandoffLimboRecoveryCount?: number | null; verificationFailureCount?: number | null; mergeConflictBounceCount?: number | null; mergeAuditBounceCount?: number | null; mergeTransientRetryCount?: number | null; branchConflictRecoveryCount?: number | null; reviewerContextRetryCount?: number | null; reviewerFallbackRetryCount?: number | null; nextRecoveryAt?: string | null; enabledWorkflowSteps?: string[]; noCommitsExpected?: boolean | null; modelProvider?: string | null; credentialInstanceId?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorCredentialInstanceId?: string | null; validatorModelId?: string | null; planningModelProvider?: string | null; planningCredentialInstanceId?: string | null; planningModelId?: string | null; mergerModelProvider?: string | null; mergerCredentialInstanceId?: string | null; mergerModelId?: string | null; thinkingLevel?: string | null; validatorThinkingLevel?: string | null; planningThinkingLevel?: string | null; mergerThinkingLevel?: string | null; error?: string | null; summary?: string | null; sessionFile?: string | null; firstExecutionAt?: string | null; cumulativeActiveMs?: number | null; cumulativePlanningMs?: number | null; planningStartedAt?: string | null; executionStartedAt?: string | null; executionCompletedAt?: string | null; review?: import("./types.js").TaskReview | null; reviewState?: import("./types.js").TaskReviewState | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null; mergeDetails?: import("./types.js").MergeDetails | null; sourceIssue?: import("./types.js").TaskSourceIssue | null; sourceMetadataPatch?: Record<string, unknown> | null; githubTracking?: import("./types.js").TaskGithubTracking | null; tokenUsage?: import("./types.js").TaskTokenUsage | null; modifiedFiles?: string[] | null; declaredSymbols?: string[] | null | undefined; missionId?: string | null; sliceId?: string | null; workflowTransitionNotification?: import("./types.js").WorkflowTransitionNotificationMarker | undefined; plannerOversightLevel?: string | null; sessionAdvisorEnabled?: boolean | null; approvedPlanFingerprint?: string | null }, runContext?: RunMutationContext,
|
||||||
): Promise<Task> {
|
): Promise<Task> {
|
||||||
return updateTaskImpl(this, id, updates, runContext);
|
return updateTaskImpl(this, id, updates, runContext);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,12 +107,16 @@ export async function taskToArchiveEntryImpl(store: TaskStore, task: Task, archi
|
|||||||
archivedAt,
|
archivedAt,
|
||||||
modelPresetId: task.modelPresetId,
|
modelPresetId: task.modelPresetId,
|
||||||
modelProvider: task.modelProvider,
|
modelProvider: task.modelProvider,
|
||||||
|
credentialInstanceId: task.credentialInstanceId,
|
||||||
modelId: task.modelId,
|
modelId: task.modelId,
|
||||||
validatorModelProvider: task.validatorModelProvider,
|
validatorModelProvider: task.validatorModelProvider,
|
||||||
|
validatorCredentialInstanceId: task.validatorCredentialInstanceId,
|
||||||
validatorModelId: task.validatorModelId,
|
validatorModelId: task.validatorModelId,
|
||||||
planningModelProvider: task.planningModelProvider,
|
planningModelProvider: task.planningModelProvider,
|
||||||
|
planningCredentialInstanceId: task.planningCredentialInstanceId,
|
||||||
planningModelId: task.planningModelId,
|
planningModelId: task.planningModelId,
|
||||||
mergerModelProvider: task.mergerModelProvider,
|
mergerModelProvider: task.mergerModelProvider,
|
||||||
|
mergerCredentialInstanceId: task.mergerCredentialInstanceId,
|
||||||
mergerModelId: task.mergerModelId,
|
mergerModelId: task.mergerModelId,
|
||||||
mergerThinkingLevel: task.mergerThinkingLevel,
|
mergerThinkingLevel: task.mergerThinkingLevel,
|
||||||
breakIntoSubtasks: task.breakIntoSubtasks,
|
breakIntoSubtasks: task.breakIntoSubtasks,
|
||||||
@@ -571,12 +575,16 @@ export async function restoreFromArchiveImpl(store: TaskStore, entry: import("..
|
|||||||
columnMovedAt: entry.columnMovedAt,
|
columnMovedAt: entry.columnMovedAt,
|
||||||
modelPresetId: entry.modelPresetId,
|
modelPresetId: entry.modelPresetId,
|
||||||
modelProvider: entry.modelProvider,
|
modelProvider: entry.modelProvider,
|
||||||
|
credentialInstanceId: entry.credentialInstanceId,
|
||||||
modelId: entry.modelId,
|
modelId: entry.modelId,
|
||||||
validatorModelProvider: entry.validatorModelProvider,
|
validatorModelProvider: entry.validatorModelProvider,
|
||||||
|
validatorCredentialInstanceId: entry.validatorCredentialInstanceId,
|
||||||
validatorModelId: entry.validatorModelId,
|
validatorModelId: entry.validatorModelId,
|
||||||
planningModelProvider: entry.planningModelProvider,
|
planningModelProvider: entry.planningModelProvider,
|
||||||
|
planningCredentialInstanceId: entry.planningCredentialInstanceId,
|
||||||
planningModelId: entry.planningModelId,
|
planningModelId: entry.planningModelId,
|
||||||
mergerModelProvider: entry.mergerModelProvider,
|
mergerModelProvider: entry.mergerModelProvider,
|
||||||
|
mergerCredentialInstanceId: entry.mergerCredentialInstanceId,
|
||||||
mergerModelId: entry.mergerModelId,
|
mergerModelId: entry.mergerModelId,
|
||||||
mergerThinkingLevel: entry.mergerThinkingLevel,
|
mergerThinkingLevel: entry.mergerThinkingLevel,
|
||||||
breakIntoSubtasks: entry.breakIntoSubtasks,
|
breakIntoSubtasks: entry.breakIntoSubtasks,
|
||||||
|
|||||||
@@ -600,7 +600,7 @@ export async function resetPromptCheckboxesImpl(store: TaskStore, dir: string):
|
|||||||
|
|
||||||
export async function updateTaskImpl(store: TaskStore,
|
export async function updateTaskImpl(store: TaskStore,
|
||||||
id: string,
|
id: string,
|
||||||
updates: { title?: string; description?: string; priority?: TaskPriority | null; prompt?: string; worktree?: string | null; workspaceWorktrees?: import("../types.js").Task["workspaceWorktrees"]; status?: string | null; dependencies?: string[]; steps?: import("../types.js").TaskStep[]; customFields?: Record<string, unknown>; currentStep?: number; blockedBy?: string | null; overlapBlockedBy?: string | null; assignedAgentId?: string | null; pausedByAgentId?: string | null; pausedReason?: string | null; tokenBudgetSoftAlertedAt?: string | null; worktrunkFallbackAlertedAt?: string | null; worktrunkFailure?: import("../types.js").Task["worktrunkFailure"] | null; tokenBudgetHardAlertedAt?: string | null; tokenBudgetOverride?: import("../types.js").TaskTokenBudgetOverride | null; dispatchStormCount?: number | null; lastDispatchAt?: string | null; assigneeUserId?: string | null; scopeOverride?: boolean | null; scopeOverrideReason?: string | null; scopeAutoWiden?: string[] | null; nodeId?: string | null; effectiveNodeId?: string | null; effectiveNodeSource?: string | null; checkedOutBy?: string | null; checkedOutAt?: string | null; checkoutNodeId?: string | null; checkoutRunId?: string | null; checkoutLeaseRenewedAt?: string | null; checkoutLeaseEpoch?: number | null; paused?: boolean; baseBranch?: string | null; autoMerge?: boolean | null; branch?: string | null; executionStartBranch?: string | null; baseCommitSha?: string | null; size?: "S" | "M" | "L"; reviewLevel?: number; executionMode?: import("../types.js").ExecutionMode | null; mergeRetries?: number; workflowStepRetries?: number; stuckKillCount?: number | null; resumeLimboCount?: number | null; executeRequeueLoopCount?: number | null; graphResumeRetryCount?: number | null; consecutiveToolFailureRetryCount?: number | null; executorEscalationAttempted?: boolean | null; toolFailureDetectorLogCursor?: number | null; toolFailureRetryExhaustedAuditEmitted?: boolean | null; resumeLimboTipSha?: string | null; resumeLimboStepSignature?: string | null; executeRequeueLoopSignature?: string | null; postReviewFixCount?: number | null; planReviewReplanCount?: number | null; recoveryRetryCount?: number | null; taskDoneRetryCount?: number | null; bulkCompletionRefusalAt?: string | null; workflowIrPin?: string | null; workflowIrPinNodeId?: string | null; workflowIrPinColumnId?: string | null; legacyAdoptedAt?: string | null; worktreeSessionRetryCount?: number | null; completionHandoffLimboRecoveryCount?: number | null; verificationFailureCount?: number | null; mergeConflictBounceCount?: number | null; mergeAuditBounceCount?: number | null; mergeTransientRetryCount?: number | null; branchConflictRecoveryCount?: number | null; reviewerContextRetryCount?: number | null; reviewerFallbackRetryCount?: number | null; nextRecoveryAt?: string | null; enabledWorkflowSteps?: string[]; noCommitsExpected?: boolean | null; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; planningModelProvider?: string | null; planningModelId?: string | null; mergerModelProvider?: string | null; mergerModelId?: string | null; thinkingLevel?: string | null; validatorThinkingLevel?: string | null; planningThinkingLevel?: string | null; mergerThinkingLevel?: string | null; error?: string | null; summary?: string | null; sessionFile?: string | null; firstExecutionAt?: string | null; cumulativeActiveMs?: number | null; executionStartedAt?: string | null; executionCompletedAt?: string | null; review?: import("../types.js").TaskReview | null; reviewState?: import("../types.js").TaskReviewState | null; workflowStepResults?: import("../types.js").WorkflowStepResult[] | null; mergeDetails?: import("../types.js").MergeDetails | null; sourceIssue?: import("../types.js").TaskSourceIssue | null; sourceMetadataPatch?: Record<string, unknown> | null; githubTracking?: import("../types.js").TaskGithubTracking | null; tokenUsage?: import("../types.js").TaskTokenUsage | null; modifiedFiles?: string[] | null; missionId?: string | null; sliceId?: string | null; workflowTransitionNotification?: import("../types.js").WorkflowTransitionNotificationMarker | undefined; sessionAdvisorEnabled?: boolean | null }, runContext?: RunMutationContext,
|
updates: { title?: string; description?: string; priority?: TaskPriority | null; prompt?: string; worktree?: string | null; workspaceWorktrees?: import("../types.js").Task["workspaceWorktrees"]; status?: string | null; dependencies?: string[]; steps?: import("../types.js").TaskStep[]; customFields?: Record<string, unknown>; currentStep?: number; blockedBy?: string | null; overlapBlockedBy?: string | null; assignedAgentId?: string | null; pausedByAgentId?: string | null; pausedReason?: string | null; tokenBudgetSoftAlertedAt?: string | null; worktrunkFallbackAlertedAt?: string | null; worktrunkFailure?: import("../types.js").Task["worktrunkFailure"] | null; tokenBudgetHardAlertedAt?: string | null; tokenBudgetOverride?: import("../types.js").TaskTokenBudgetOverride | null; dispatchStormCount?: number | null; lastDispatchAt?: string | null; assigneeUserId?: string | null; scopeOverride?: boolean | null; scopeOverrideReason?: string | null; scopeAutoWiden?: string[] | null; nodeId?: string | null; effectiveNodeId?: string | null; effectiveNodeSource?: string | null; checkedOutBy?: string | null; checkedOutAt?: string | null; checkoutNodeId?: string | null; checkoutRunId?: string | null; checkoutLeaseRenewedAt?: string | null; checkoutLeaseEpoch?: number | null; paused?: boolean; baseBranch?: string | null; autoMerge?: boolean | null; branch?: string | null; executionStartBranch?: string | null; baseCommitSha?: string | null; size?: "S" | "M" | "L"; reviewLevel?: number; executionMode?: import("../types.js").ExecutionMode | null; mergeRetries?: number; workflowStepRetries?: number; stuckKillCount?: number | null; resumeLimboCount?: number | null; executeRequeueLoopCount?: number | null; graphResumeRetryCount?: number | null; consecutiveToolFailureRetryCount?: number | null; executorEscalationAttempted?: boolean | null; toolFailureDetectorLogCursor?: number | null; toolFailureRetryExhaustedAuditEmitted?: boolean | null; resumeLimboTipSha?: string | null; resumeLimboStepSignature?: string | null; executeRequeueLoopSignature?: string | null; postReviewFixCount?: number | null; planReviewReplanCount?: number | null; recoveryRetryCount?: number | null; taskDoneRetryCount?: number | null; bulkCompletionRefusalAt?: string | null; workflowIrPin?: string | null; workflowIrPinNodeId?: string | null; workflowIrPinColumnId?: string | null; legacyAdoptedAt?: string | null; worktreeSessionRetryCount?: number | null; completionHandoffLimboRecoveryCount?: number | null; verificationFailureCount?: number | null; mergeConflictBounceCount?: number | null; mergeAuditBounceCount?: number | null; mergeTransientRetryCount?: number | null; branchConflictRecoveryCount?: number | null; reviewerContextRetryCount?: number | null; reviewerFallbackRetryCount?: number | null; nextRecoveryAt?: string | null; enabledWorkflowSteps?: string[]; noCommitsExpected?: boolean | null; modelProvider?: string | null; credentialInstanceId?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorCredentialInstanceId?: string | null; validatorModelId?: string | null; planningModelProvider?: string | null; planningCredentialInstanceId?: string | null; planningModelId?: string | null; mergerModelProvider?: string | null; mergerCredentialInstanceId?: string | null; mergerModelId?: string | null; thinkingLevel?: string | null; validatorThinkingLevel?: string | null; planningThinkingLevel?: string | null; mergerThinkingLevel?: string | null; error?: string | null; summary?: string | null; sessionFile?: string | null; firstExecutionAt?: string | null; cumulativeActiveMs?: number | null; executionStartedAt?: string | null; executionCompletedAt?: string | null; review?: import("../types.js").TaskReview | null; reviewState?: import("../types.js").TaskReviewState | null; workflowStepResults?: import("../types.js").WorkflowStepResult[] | null; mergeDetails?: import("../types.js").MergeDetails | null; sourceIssue?: import("../types.js").TaskSourceIssue | null; sourceMetadataPatch?: Record<string, unknown> | null; githubTracking?: import("../types.js").TaskGithubTracking | null; tokenUsage?: import("../types.js").TaskTokenUsage | null; modifiedFiles?: string[] | null; missionId?: string | null; sliceId?: string | null; workflowTransitionNotification?: import("../types.js").WorkflowTransitionNotificationMarker | undefined; sessionAdvisorEnabled?: boolean | null }, runContext?: RunMutationContext,
|
||||||
): Promise<Task> {
|
): Promise<Task> {
|
||||||
/*
|
/*
|
||||||
FNXC:StateMachine 2026-07-07-12:00:
|
FNXC:StateMachine 2026-07-07-12:00:
|
||||||
|
|||||||
@@ -38,12 +38,16 @@ export interface TaskRow {
|
|||||||
baseCommitSha: string | null;
|
baseCommitSha: string | null;
|
||||||
modelPresetId: string | null;
|
modelPresetId: string | null;
|
||||||
modelProvider: string | null;
|
modelProvider: string | null;
|
||||||
|
credentialInstanceId: string | null;
|
||||||
modelId: string | null;
|
modelId: string | null;
|
||||||
validatorModelProvider: string | null;
|
validatorModelProvider: string | null;
|
||||||
|
validatorCredentialInstanceId: string | null;
|
||||||
validatorModelId: string | null;
|
validatorModelId: string | null;
|
||||||
planningModelProvider: string | null;
|
planningModelProvider: string | null;
|
||||||
|
planningCredentialInstanceId: string | null;
|
||||||
planningModelId: string | null;
|
planningModelId: string | null;
|
||||||
mergerModelProvider: string | null;
|
mergerModelProvider: string | null;
|
||||||
|
mergerCredentialInstanceId: string | null;
|
||||||
mergerModelId: string | null;
|
mergerModelId: string | null;
|
||||||
mergeRetries: number | null;
|
mergeRetries: number | null;
|
||||||
workflowStepRetries: number | null;
|
workflowStepRetries: number | null;
|
||||||
@@ -255,12 +259,16 @@ export const TASK_COLUMN_DESCRIPTORS: TaskColumnDescriptor[] = [
|
|||||||
defineTaskColumn("baseCommitSha", (task) => task.baseCommitSha ?? null),
|
defineTaskColumn("baseCommitSha", (task) => task.baseCommitSha ?? null),
|
||||||
defineTaskColumn("modelPresetId", (task) => task.modelPresetId ?? null),
|
defineTaskColumn("modelPresetId", (task) => task.modelPresetId ?? null),
|
||||||
defineTaskColumn("modelProvider", (task) => task.modelProvider ?? null),
|
defineTaskColumn("modelProvider", (task) => task.modelProvider ?? null),
|
||||||
|
defineTaskColumn("credentialInstanceId", (task) => task.credentialInstanceId ?? null),
|
||||||
defineTaskColumn("modelId", (task) => task.modelId ?? null),
|
defineTaskColumn("modelId", (task) => task.modelId ?? null),
|
||||||
defineTaskColumn("validatorModelProvider", (task) => task.validatorModelProvider ?? null),
|
defineTaskColumn("validatorModelProvider", (task) => task.validatorModelProvider ?? null),
|
||||||
|
defineTaskColumn("validatorCredentialInstanceId", (task) => task.validatorCredentialInstanceId ?? null),
|
||||||
defineTaskColumn("validatorModelId", (task) => task.validatorModelId ?? null),
|
defineTaskColumn("validatorModelId", (task) => task.validatorModelId ?? null),
|
||||||
defineTaskColumn("planningModelProvider", (task) => task.planningModelProvider ?? null),
|
defineTaskColumn("planningModelProvider", (task) => task.planningModelProvider ?? null),
|
||||||
|
defineTaskColumn("planningCredentialInstanceId", (task) => task.planningCredentialInstanceId ?? null),
|
||||||
defineTaskColumn("planningModelId", (task) => task.planningModelId ?? null),
|
defineTaskColumn("planningModelId", (task) => task.planningModelId ?? null),
|
||||||
defineTaskColumn("mergerModelProvider", (task) => task.mergerModelProvider ?? null),
|
defineTaskColumn("mergerModelProvider", (task) => task.mergerModelProvider ?? null),
|
||||||
|
defineTaskColumn("mergerCredentialInstanceId", (task) => task.mergerCredentialInstanceId ?? null),
|
||||||
defineTaskColumn("mergerModelId", (task) => task.mergerModelId ?? null),
|
defineTaskColumn("mergerModelId", (task) => task.mergerModelId ?? null),
|
||||||
defineTaskColumn("mergeRetries", (task) => task.mergeRetries ?? null),
|
defineTaskColumn("mergeRetries", (task) => task.mergeRetries ?? null),
|
||||||
defineTaskColumn("workflowStepRetries", (task) => task.workflowStepRetries ?? null),
|
defineTaskColumn("workflowStepRetries", (task) => task.workflowStepRetries ?? null),
|
||||||
|
|||||||
@@ -93,12 +93,18 @@ export function rowToTask(row: TaskRow): Task {
|
|||||||
scopeAutoWiden: fromJson<string[]>(row.scopeAutoWiden) ?? [],
|
scopeAutoWiden: fromJson<string[]>(row.scopeAutoWiden) ?? [],
|
||||||
modelPresetId: row.modelPresetId || undefined,
|
modelPresetId: row.modelPresetId || undefined,
|
||||||
modelProvider: row.modelProvider || undefined,
|
modelProvider: row.modelProvider || undefined,
|
||||||
|
// FNXC:CredentialInstanceSelection 2026-08-01-05:53: database NULL means omitted,
|
||||||
|
// not an own `undefined` key, so legacy task reads stay byte-identical in this inert slice.
|
||||||
|
...(row.credentialInstanceId ? { credentialInstanceId: row.credentialInstanceId } : {}),
|
||||||
modelId: row.modelId || undefined,
|
modelId: row.modelId || undefined,
|
||||||
validatorModelProvider: row.validatorModelProvider || undefined,
|
validatorModelProvider: row.validatorModelProvider || undefined,
|
||||||
|
...(row.validatorCredentialInstanceId ? { validatorCredentialInstanceId: row.validatorCredentialInstanceId } : {}),
|
||||||
validatorModelId: row.validatorModelId || undefined,
|
validatorModelId: row.validatorModelId || undefined,
|
||||||
planningModelProvider: row.planningModelProvider || undefined,
|
planningModelProvider: row.planningModelProvider || undefined,
|
||||||
|
...(row.planningCredentialInstanceId ? { planningCredentialInstanceId: row.planningCredentialInstanceId } : {}),
|
||||||
planningModelId: row.planningModelId || undefined,
|
planningModelId: row.planningModelId || undefined,
|
||||||
mergerModelProvider: row.mergerModelProvider || undefined,
|
mergerModelProvider: row.mergerModelProvider || undefined,
|
||||||
|
...(row.mergerCredentialInstanceId ? { mergerCredentialInstanceId: row.mergerCredentialInstanceId } : {}),
|
||||||
mergerModelId: row.mergerModelId || undefined,
|
mergerModelId: row.mergerModelId || undefined,
|
||||||
mergeRetries: row.mergeRetries ?? undefined,
|
mergeRetries: row.mergeRetries ?? undefined,
|
||||||
workflowStepRetries: row.workflowStepRetries ?? undefined,
|
workflowStepRetries: row.workflowStepRetries ?? undefined,
|
||||||
|
|||||||
@@ -18,6 +18,34 @@ import {__setTaskActivityLogLimitsForTesting} from "../task-store/comments.js";
|
|||||||
import {isPlainObject, deepMergeWithNullDelete} from "../task-store/settings-helpers.js";
|
import {isPlainObject, deepMergeWithNullDelete} from "../task-store/settings-helpers.js";
|
||||||
import {readProjectConfig as readProjectConfigAsync, writeProjectConfig as writeProjectConfigAsync} from "../task-store/async-settings.js";
|
import {readProjectConfig as readProjectConfigAsync, writeProjectConfig as writeProjectConfigAsync} from "../task-store/async-settings.js";
|
||||||
import {appendConfigurationRevision, createConfigurationRevision} from "../async-configuration-revision-store.js";
|
import {appendConfigurationRevision, createConfigurationRevision} from "../async-configuration-revision-store.js";
|
||||||
|
import {isValidProviderInstanceId} from "../provider-instance.js";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FNXC:CredentialInstanceSelection 2026-08-01-05:38:
|
||||||
|
* Settings authoring validates persisted-but-inert credential instance ids before either project
|
||||||
|
* or global persistence. Nested presets are atomic: one malformed element rejects the whole write.
|
||||||
|
*/
|
||||||
|
function assertValidCredentialInstanceSettingsPatch(patch: Record<string, unknown>): void {
|
||||||
|
for (const [key, value] of Object.entries(patch)) {
|
||||||
|
if (key.endsWith("CredentialInstanceId") || key === "defaultCredentialInstanceIdOverride") {
|
||||||
|
if (value !== null && value !== undefined && !isValidProviderInstanceId(value)) {
|
||||||
|
throw new Error(`invalid credential instance id for settings key '${key}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (patch.modelPresets !== null && patch.modelPresets !== undefined) {
|
||||||
|
if (!Array.isArray(patch.modelPresets)) throw new Error("modelPresets must be an array");
|
||||||
|
for (const [index, preset] of patch.modelPresets.entries()) {
|
||||||
|
if (typeof preset !== "object" || preset === null) throw new Error(`modelPresets[${index}] must be an object`);
|
||||||
|
for (const key of ["executorCredentialInstanceId", "validatorCredentialInstanceId"] as const) {
|
||||||
|
const value = (preset as Record<string, unknown>)[key];
|
||||||
|
if (value !== undefined && !isValidProviderInstanceId(value)) {
|
||||||
|
throw new Error(`invalid credential instance id for modelPresets[${index}].${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Publish committed setting snapshots and run the normal post-commit effects. */
|
/** Publish committed setting snapshots and run the normal post-commit effects. */
|
||||||
export async function publishSettingsUpdated(store: TaskStore, previous: Settings, settings: Settings): Promise<void> {
|
export async function publishSettingsUpdated(store: TaskStore, previous: Settings, settings: Settings): Promise<void> {
|
||||||
@@ -30,6 +58,7 @@ export async function publishSettingsUpdated(store: TaskStore, previous: Setting
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateSettingsImpl(store: TaskStore, patch: Partial<Settings>, changedBy: ConfigChangedBy = { kind: "human", id: "local-user" }): Promise<Settings> {
|
export async function updateSettingsImpl(store: TaskStore, patch: Partial<Settings>, changedBy: ConfigChangedBy = { kind: "human", id: "local-user" }): Promise<Settings> {
|
||||||
|
assertValidCredentialInstanceSettingsPatch(patch as Record<string, unknown>);
|
||||||
/*
|
/*
|
||||||
FNXC:ConfigVersioning 2026-07-18-12:15:
|
FNXC:ConfigVersioning 2026-07-18-12:15:
|
||||||
Keep the compatibility SQLite settings path writable while projects migrate
|
Keep the compatibility SQLite settings path writable while projects migrate
|
||||||
@@ -165,6 +194,7 @@ export async function updateSettingsImpl(store: TaskStore, patch: Partial<Settin
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateGlobalSettingsImpl(store: TaskStore, patch: Partial<GlobalSettings>, changedBy: ConfigChangedBy = { kind: "human", id: "local-user" }): Promise<Settings> {
|
export async function updateGlobalSettingsImpl(store: TaskStore, patch: Partial<GlobalSettings>, changedBy: ConfigChangedBy = { kind: "human", id: "local-user" }): Promise<Settings> {
|
||||||
|
assertValidCredentialInstanceSettingsPatch(patch as Record<string, unknown>);
|
||||||
// Read previous state BEFORE writing so the diff is correct
|
// Read previous state BEFORE writing so the diff is correct
|
||||||
const previousGlobal = await store.globalSettingsStore.getSettings();
|
const previousGlobal = await store.globalSettingsStore.getSettings();
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {softDeleteTaskRow as softDeleteTaskRowAsync, insertTaskRowInTransaction,
|
|||||||
import {recordRunAuditEvent as recordRunAuditEventAsync} from "../task-store/async-audit.js";
|
import {recordRunAuditEvent as recordRunAuditEventAsync} from "../task-store/async-audit.js";
|
||||||
import type {DbTransaction} from "../postgres/data-layer.js";
|
import type {DbTransaction} from "../postgres/data-layer.js";
|
||||||
import { resolveTaskPrefix } from "./task-prefix.js";
|
import { resolveTaskPrefix } from "./task-prefix.js";
|
||||||
|
import {assertValidProviderInstanceId} from "../provider-instance.js";
|
||||||
|
|
||||||
type CreateTaskWithAfterInsert = TaskCreateInput & {
|
type CreateTaskWithAfterInsert = TaskCreateInput & {
|
||||||
/** Internal transaction hook; never persisted in task source metadata. */
|
/** Internal transaction hook; never persisted in task source metadata. */
|
||||||
@@ -148,7 +149,14 @@ async function resolveDefaultWorkflowIntakeColumn(store: TaskStore): Promise<str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createTaskBackendImpl(store: TaskStore, input: TaskCreateInput, options?: { onSummarize?: (description: string) => Promise<string | null>; settings?: { autoSummarizeTitles?: boolean }; invokeTaskCreatedHook?: boolean; onProposalClaimConflict?: (task: Task) => void; },): Promise<Task> {
|
export async function createTaskBackendImpl(store: TaskStore, input: TaskCreateInput, options?: {
|
||||||
|
onSummarize?: (description: string) => Promise<string | null>; settings?: { autoSummarizeTitles?: boolean }; invokeTaskCreatedHook?: boolean; onProposalClaimConflict?: (task: Task) => void; },): Promise<Task> {
|
||||||
|
/* FNXC:CredentialInstanceSelection 2026-08-01-05:43: validate task authoring input before persistence; ids are stored but runtime credential resolution remains unchanged. */
|
||||||
|
for (const key of ["credentialInstanceId", "validatorCredentialInstanceId", "planningCredentialInstanceId", "mergerCredentialInstanceId"] as const) {
|
||||||
|
const value = (input as unknown as Record<string, unknown>)[key];
|
||||||
|
if (value !== undefined && value !== null) assertValidProviderInstanceId(value);
|
||||||
|
}
|
||||||
|
|
||||||
// U8/R6: apply the reviewLevel creation-time preset (maps level -> enabledWorkflowSteps; explicit wins).
|
// U8/R6: apply the reviewLevel creation-time preset (maps level -> enabledWorkflowSteps; explicit wins).
|
||||||
input = applyReviewLevelPreset(input);
|
input = applyReviewLevelPreset(input);
|
||||||
if (!input.description?.trim()) {
|
if (!input.description?.trim()) {
|
||||||
@@ -471,12 +479,16 @@ export async function _createTaskInternalBackendImpl(store: TaskStore, input: Ta
|
|||||||
scopeOverrideReason: input.scopeOverrideReason,
|
scopeOverrideReason: input.scopeOverrideReason,
|
||||||
nodeId: input.nodeId,
|
nodeId: input.nodeId,
|
||||||
modelProvider: input.modelProvider,
|
modelProvider: input.modelProvider,
|
||||||
|
credentialInstanceId: input.credentialInstanceId,
|
||||||
modelId: input.modelId,
|
modelId: input.modelId,
|
||||||
validatorModelProvider: input.validatorModelProvider,
|
validatorModelProvider: input.validatorModelProvider,
|
||||||
|
validatorCredentialInstanceId: input.validatorCredentialInstanceId,
|
||||||
validatorModelId: input.validatorModelId,
|
validatorModelId: input.validatorModelId,
|
||||||
planningModelProvider: input.planningModelProvider,
|
planningModelProvider: input.planningModelProvider,
|
||||||
|
planningCredentialInstanceId: input.planningCredentialInstanceId,
|
||||||
planningModelId: input.planningModelId,
|
planningModelId: input.planningModelId,
|
||||||
mergerModelProvider: input.mergerModelProvider,
|
mergerModelProvider: input.mergerModelProvider,
|
||||||
|
mergerCredentialInstanceId: input.mergerCredentialInstanceId,
|
||||||
mergerModelId: input.mergerModelId,
|
mergerModelId: input.mergerModelId,
|
||||||
thinkingLevel: input.thinkingLevel,
|
thinkingLevel: input.thinkingLevel,
|
||||||
validatorThinkingLevel: input.validatorThinkingLevel,
|
validatorThinkingLevel: input.validatorThinkingLevel,
|
||||||
@@ -941,12 +953,16 @@ export async function _createTaskInternalImpl(store: TaskStore, input: TaskCreat
|
|||||||
scopeOverrideReason: input.scopeOverrideReason,
|
scopeOverrideReason: input.scopeOverrideReason,
|
||||||
nodeId: input.nodeId,
|
nodeId: input.nodeId,
|
||||||
modelProvider: input.modelProvider,
|
modelProvider: input.modelProvider,
|
||||||
|
credentialInstanceId: input.credentialInstanceId,
|
||||||
modelId: input.modelId,
|
modelId: input.modelId,
|
||||||
validatorModelProvider: input.validatorModelProvider,
|
validatorModelProvider: input.validatorModelProvider,
|
||||||
|
validatorCredentialInstanceId: input.validatorCredentialInstanceId,
|
||||||
validatorModelId: input.validatorModelId,
|
validatorModelId: input.validatorModelId,
|
||||||
planningModelProvider: input.planningModelProvider,
|
planningModelProvider: input.planningModelProvider,
|
||||||
|
planningCredentialInstanceId: input.planningCredentialInstanceId,
|
||||||
planningModelId: input.planningModelId,
|
planningModelId: input.planningModelId,
|
||||||
mergerModelProvider: input.mergerModelProvider,
|
mergerModelProvider: input.mergerModelProvider,
|
||||||
|
mergerCredentialInstanceId: input.mergerCredentialInstanceId,
|
||||||
mergerModelId: input.mergerModelId,
|
mergerModelId: input.mergerModelId,
|
||||||
thinkingLevel: input.thinkingLevel,
|
thinkingLevel: input.thinkingLevel,
|
||||||
validatorThinkingLevel: input.validatorThinkingLevel,
|
validatorThinkingLevel: input.validatorThinkingLevel,
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ export function getTaskSelectClauseWithActivityLogLimitImpl(store: TaskStore, li
|
|||||||
const columns = [
|
const columns = [
|
||||||
"id", "lineageId", "title", "description", "priority", "\"column\"", "status", "size", "reviewLevel", "currentStep",
|
"id", "lineageId", "title", "description", "priority", "\"column\"", "status", "size", "reviewLevel", "currentStep",
|
||||||
"worktree", "blockedBy", "overlapBlockedBy", "paused", "pausedReason", "userPaused", "baseBranch", "branch", "autoMerge", "autoMergeProvenance", "executionStartBranch", "baseCommitSha",
|
"worktree", "blockedBy", "overlapBlockedBy", "paused", "pausedReason", "userPaused", "baseBranch", "branch", "autoMerge", "autoMergeProvenance", "executionStartBranch", "baseCommitSha",
|
||||||
"modelPresetId", "modelProvider", "modelId",
|
"modelPresetId", "modelProvider", "credentialInstanceId", "modelId",
|
||||||
"validatorModelProvider", "validatorModelId",
|
"validatorModelProvider", "validatorCredentialInstanceId", "validatorModelId",
|
||||||
"planningModelProvider", "planningModelId", "mergerModelProvider", "mergerModelId",
|
"planningModelProvider", "planningCredentialInstanceId", "planningModelId", "mergerModelProvider", "mergerCredentialInstanceId", "mergerModelId",
|
||||||
"mergeRetries", "workflowStepRetries", "stuckKillCount", "resumeLimboCount", "executeRequeueLoopCount", "graphResumeRetryCount", "consecutiveToolFailureRetryCount", "executorEscalationAttempted", "toolFailureDetectorLogCursor", "toolFailureRetryExhaustedAuditEmitted", "resumeLimboTipSha", "resumeLimboStepSignature", "executeRequeueLoopSignature", "postReviewFixCount", "planReviewReplanCount", "recoveryRetryCount", "taskDoneRetryCount", "bulkCompletionRefusalAt", "worktreeSessionRetryCount", "completionHandoffLimboRecoveryCount", "verificationFailureCount", "mergeConflictBounceCount", "mergeAuditBounceCount", "mergeTransientRetryCount", "branchConflictRecoveryCount", "reviewerContextRetryCount", "reviewerFallbackRetryCount", "nextRecoveryAt",
|
"mergeRetries", "workflowStepRetries", "stuckKillCount", "resumeLimboCount", "executeRequeueLoopCount", "graphResumeRetryCount", "consecutiveToolFailureRetryCount", "executorEscalationAttempted", "toolFailureDetectorLogCursor", "toolFailureRetryExhaustedAuditEmitted", "resumeLimboTipSha", "resumeLimboStepSignature", "executeRequeueLoopSignature", "postReviewFixCount", "planReviewReplanCount", "recoveryRetryCount", "taskDoneRetryCount", "bulkCompletionRefusalAt", "worktreeSessionRetryCount", "completionHandoffLimboRecoveryCount", "verificationFailureCount", "mergeConflictBounceCount", "mergeAuditBounceCount", "mergeTransientRetryCount", "branchConflictRecoveryCount", "reviewerContextRetryCount", "reviewerFallbackRetryCount", "nextRecoveryAt",
|
||||||
// FNXC:WorkflowIrPin 2026-07-19-03:10 (U9b / KTD-3 + KTD-8): this projection is a SECOND
|
// FNXC:WorkflowIrPin 2026-07-19-03:10 (U9b / KTD-3 + KTD-8): this projection is a SECOND
|
||||||
// copy of the slim column list (see getTaskSelectClauseImpl2). The IR pin, its node entry,
|
// copy of the slim column list (see getTaskSelectClauseImpl2). The IR pin, its node entry,
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ export function getTaskSelectClauseImpl2(store: TaskStore, slim: boolean, tableA
|
|||||||
return [
|
return [
|
||||||
"id", "lineageId", "title", "description", "priority", "\"column\"", "status", "size", "reviewLevel", "currentStep",
|
"id", "lineageId", "title", "description", "priority", "\"column\"", "status", "size", "reviewLevel", "currentStep",
|
||||||
"worktree", "blockedBy", "overlapBlockedBy", "paused", "pausedReason", "wedgeNotification", "userPaused", "baseBranch", "branch", "autoMerge", "autoMergeProvenance", "executionStartBranch", "baseCommitSha",
|
"worktree", "blockedBy", "overlapBlockedBy", "paused", "pausedReason", "wedgeNotification", "userPaused", "baseBranch", "branch", "autoMerge", "autoMergeProvenance", "executionStartBranch", "baseCommitSha",
|
||||||
"modelPresetId", "modelProvider", "modelId",
|
"modelPresetId", "modelProvider", "credentialInstanceId", "modelId",
|
||||||
"validatorModelProvider", "validatorModelId",
|
"validatorModelProvider", "validatorCredentialInstanceId", "validatorModelId",
|
||||||
"planningModelProvider", "planningModelId", "mergerModelProvider", "mergerModelId",
|
"planningModelProvider", "planningCredentialInstanceId", "planningModelId", "mergerModelProvider", "mergerCredentialInstanceId", "mergerModelId",
|
||||||
"mergeRetries", "workflowStepRetries", "stuckKillCount", "resumeLimboCount", "executeRequeueLoopCount", "graphResumeRetryCount", "consecutiveToolFailureRetryCount", "executorEscalationAttempted", "toolFailureDetectorLogCursor", "toolFailureRetryExhaustedAuditEmitted", "resumeLimboTipSha", "resumeLimboStepSignature", "executeRequeueLoopSignature", "postReviewFixCount", "planReviewReplanCount", "recoveryRetryCount", "taskDoneRetryCount", "bulkCompletionRefusalAt", "worktreeSessionRetryCount", "completionHandoffLimboRecoveryCount", "verificationFailureCount", "mergeConflictBounceCount", "mergeAuditBounceCount", "mergeTransientRetryCount", "branchConflictRecoveryCount", "reviewerContextRetryCount", "reviewerFallbackRetryCount", "nextRecoveryAt",
|
"mergeRetries", "workflowStepRetries", "stuckKillCount", "resumeLimboCount", "executeRequeueLoopCount", "graphResumeRetryCount", "consecutiveToolFailureRetryCount", "executorEscalationAttempted", "toolFailureDetectorLogCursor", "toolFailureRetryExhaustedAuditEmitted", "resumeLimboTipSha", "resumeLimboStepSignature", "executeRequeueLoopSignature", "postReviewFixCount", "planReviewReplanCount", "recoveryRetryCount", "taskDoneRetryCount", "bulkCompletionRefusalAt", "worktreeSessionRetryCount", "completionHandoffLimboRecoveryCount", "verificationFailureCount", "mergeConflictBounceCount", "mergeAuditBounceCount", "mergeTransientRetryCount", "branchConflictRecoveryCount", "reviewerContextRetryCount", "reviewerFallbackRetryCount", "nextRecoveryAt",
|
||||||
"error", "summary", "thinkingLevel", "validatorThinkingLevel", "planningThinkingLevel", "mergerThinkingLevel", "executionMode",
|
"error", "summary", "thinkingLevel", "validatorThinkingLevel", "planningThinkingLevel", "mergerThinkingLevel", "executionMode",
|
||||||
"tokenUsageInputTokens", "tokenUsageOutputTokens", "tokenUsageCachedTokens", "tokenUsageCacheWriteTokens", "tokenUsageTotalTokens", "tokenUsageFirstUsedAt", "tokenUsageLastUsedAt", "tokenUsageModelProvider", "tokenUsageModelId", "tokenUsagePerModel", "tokenBudgetSoftAlertedAt", "tokenBudgetHardAlertedAt", "tokenBudgetOverride",
|
"tokenUsageInputTokens", "tokenUsageOutputTokens", "tokenUsageCachedTokens", "tokenUsageCacheWriteTokens", "tokenUsageTotalTokens", "tokenUsageFirstUsedAt", "tokenUsageLastUsedAt", "tokenUsageModelProvider", "tokenUsageModelId", "tokenUsagePerModel", "tokenBudgetSoftAlertedAt", "tokenBudgetHardAlertedAt", "tokenBudgetOverride",
|
||||||
|
|||||||
@@ -27,8 +27,15 @@ import {__setTaskActivityLogLimitsForTesting, isBootstrapPromptStub, rewriteHead
|
|||||||
import {applyOriginalDescription} from "../original-description-policy.js";
|
import {applyOriginalDescription} from "../original-description-policy.js";
|
||||||
import {normalizeTaskReviewState} from "../task-store/review-state.js";
|
import {normalizeTaskReviewState} from "../task-store/review-state.js";
|
||||||
import {hasOwnDeclaredSymbols, normalizeDeclaredSymbols, extractDeclaredSymbolsFromPrompt, resolveTaskSymbolsForTask} from "../task-symbol-resolution.js";
|
import {hasOwnDeclaredSymbols, normalizeDeclaredSymbols, extractDeclaredSymbolsFromPrompt, resolveTaskSymbolsForTask} from "../task-symbol-resolution.js";
|
||||||
|
import {assertValidProviderInstanceId} from "../provider-instance.js";
|
||||||
|
|
||||||
export async function updateTaskUnlockedImpl(store: TaskStore, id: string, updates: Parameters<TaskStore["updateTask"]>[1], runContext?: RunMutationContext,): Promise<Task> {
|
export async function updateTaskUnlockedImpl(store: TaskStore, id: string, updates: Parameters<TaskStore["updateTask"]>[1], runContext?: RunMutationContext,): Promise<Task> {
|
||||||
|
/* FNXC:CredentialInstanceSelection 2026-08-01-05:43: validate task authoring input before persistence; ids are stored but runtime credential resolution remains unchanged. */
|
||||||
|
for (const key of ["credentialInstanceId", "validatorCredentialInstanceId", "planningCredentialInstanceId", "mergerCredentialInstanceId"] as const) {
|
||||||
|
const value = (updates as Record<string, unknown>)[key];
|
||||||
|
if (value !== undefined && value !== null) assertValidProviderInstanceId(value);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
if (updates.dependencies !== undefined) {
|
if (updates.dependencies !== undefined) {
|
||||||
await store.assertNoDependencyCycle(
|
await store.assertNoDependencyCycle(
|
||||||
@@ -643,6 +650,11 @@ export async function updateTaskUnlockedImpl(store: TaskStore, id: string, updat
|
|||||||
} else if (updates.modelProvider !== undefined) {
|
} else if (updates.modelProvider !== undefined) {
|
||||||
task.modelProvider = updates.modelProvider;
|
task.modelProvider = updates.modelProvider;
|
||||||
}
|
}
|
||||||
|
if (updates.credentialInstanceId === null) {
|
||||||
|
task.credentialInstanceId = undefined;
|
||||||
|
} else if (updates.credentialInstanceId !== undefined) {
|
||||||
|
task.credentialInstanceId = updates.credentialInstanceId;
|
||||||
|
}
|
||||||
if (updates.modelId === null) {
|
if (updates.modelId === null) {
|
||||||
task.modelId = undefined;
|
task.modelId = undefined;
|
||||||
} else if (updates.modelId !== undefined) {
|
} else if (updates.modelId !== undefined) {
|
||||||
@@ -653,6 +665,11 @@ export async function updateTaskUnlockedImpl(store: TaskStore, id: string, updat
|
|||||||
} else if (updates.validatorModelProvider !== undefined) {
|
} else if (updates.validatorModelProvider !== undefined) {
|
||||||
task.validatorModelProvider = updates.validatorModelProvider;
|
task.validatorModelProvider = updates.validatorModelProvider;
|
||||||
}
|
}
|
||||||
|
if (updates.validatorCredentialInstanceId === null) {
|
||||||
|
task.validatorCredentialInstanceId = undefined;
|
||||||
|
} else if (updates.validatorCredentialInstanceId !== undefined) {
|
||||||
|
task.validatorCredentialInstanceId = updates.validatorCredentialInstanceId;
|
||||||
|
}
|
||||||
if (updates.validatorModelId === null) {
|
if (updates.validatorModelId === null) {
|
||||||
task.validatorModelId = undefined;
|
task.validatorModelId = undefined;
|
||||||
} else if (updates.validatorModelId !== undefined) {
|
} else if (updates.validatorModelId !== undefined) {
|
||||||
@@ -663,6 +680,11 @@ export async function updateTaskUnlockedImpl(store: TaskStore, id: string, updat
|
|||||||
} else if (updates.planningModelProvider !== undefined) {
|
} else if (updates.planningModelProvider !== undefined) {
|
||||||
task.planningModelProvider = updates.planningModelProvider;
|
task.planningModelProvider = updates.planningModelProvider;
|
||||||
}
|
}
|
||||||
|
if (updates.planningCredentialInstanceId === null) {
|
||||||
|
task.planningCredentialInstanceId = undefined;
|
||||||
|
} else if (updates.planningCredentialInstanceId !== undefined) {
|
||||||
|
task.planningCredentialInstanceId = updates.planningCredentialInstanceId;
|
||||||
|
}
|
||||||
if (updates.planningModelId === null) {
|
if (updates.planningModelId === null) {
|
||||||
task.planningModelId = undefined;
|
task.planningModelId = undefined;
|
||||||
} else if (updates.planningModelId !== undefined) {
|
} else if (updates.planningModelId !== undefined) {
|
||||||
@@ -670,6 +692,8 @@ export async function updateTaskUnlockedImpl(store: TaskStore, id: string, updat
|
|||||||
}
|
}
|
||||||
if (updates.mergerModelProvider === null) task.mergerModelProvider = undefined;
|
if (updates.mergerModelProvider === null) task.mergerModelProvider = undefined;
|
||||||
else if (updates.mergerModelProvider !== undefined) task.mergerModelProvider = updates.mergerModelProvider;
|
else if (updates.mergerModelProvider !== undefined) task.mergerModelProvider = updates.mergerModelProvider;
|
||||||
|
if (updates.mergerCredentialInstanceId === null) task.mergerCredentialInstanceId = undefined;
|
||||||
|
else if (updates.mergerCredentialInstanceId !== undefined) task.mergerCredentialInstanceId = updates.mergerCredentialInstanceId;
|
||||||
if (updates.mergerModelId === null) task.mergerModelId = undefined;
|
if (updates.mergerModelId === null) task.mergerModelId = undefined;
|
||||||
else if (updates.mergerModelId !== undefined) task.mergerModelId = updates.mergerModelId;
|
else if (updates.mergerModelId !== undefined) task.mergerModelId = updates.mergerModelId;
|
||||||
if (updates.validatorThinkingLevel === null) {
|
if (updates.validatorThinkingLevel === null) {
|
||||||
|
|||||||
@@ -104,11 +104,14 @@ export interface ArchivedTaskEntry {
|
|||||||
deletedAt?: string;
|
deletedAt?: string;
|
||||||
/** Timestamp when the task was archived to the log */
|
/** Timestamp when the task was archived to the log */
|
||||||
archivedAt: string;
|
archivedAt: string;
|
||||||
|
/** FNXC:CredentialInstanceSelection 2026-08-01-05:43: archive entries preserve persisted-but-inert credential-instance companions for every task model pair. */
|
||||||
/** Optional: model preset and override fields for executor and validator */
|
/** Optional: model preset and override fields for executor and validator */
|
||||||
modelPresetId?: string;
|
modelPresetId?: string;
|
||||||
modelProvider?: string;
|
modelProvider?: string;
|
||||||
|
credentialInstanceId?: string;
|
||||||
modelId?: string;
|
modelId?: string;
|
||||||
validatorModelProvider?: string;
|
validatorModelProvider?: string;
|
||||||
|
validatorCredentialInstanceId?: string;
|
||||||
validatorModelId?: string;
|
validatorModelId?: string;
|
||||||
/**
|
/**
|
||||||
* Optional provider/model override for the planning session — the same provider/model pair
|
* Optional provider/model override for the planning session — the same provider/model pair
|
||||||
@@ -121,8 +124,10 @@ export interface ArchivedTaskEntry {
|
|||||||
* codebase — this field has never had anything to do with a column.
|
* codebase — this field has never had anything to do with a column.
|
||||||
*/
|
*/
|
||||||
planningModelProvider?: string;
|
planningModelProvider?: string;
|
||||||
|
planningCredentialInstanceId?: string;
|
||||||
planningModelId?: string;
|
planningModelId?: string;
|
||||||
mergerModelProvider?: string;
|
mergerModelProvider?: string;
|
||||||
|
mergerCredentialInstanceId?: string;
|
||||||
mergerModelId?: string;
|
mergerModelId?: string;
|
||||||
mergerThinkingLevel?: ThinkingLevel;
|
mergerThinkingLevel?: ThinkingLevel;
|
||||||
/** Per-task token/cost accounting (input/output/cache) preserved across archival. */
|
/** Per-task token/cost accounting (input/output/cache) preserved across archival. */
|
||||||
|
|||||||
@@ -361,6 +361,8 @@ export interface GlobalSettings {
|
|||||||
* Must be set together with `defaultModelId`. When both are undefined,
|
* Must be set together with `defaultModelId`. When both are undefined,
|
||||||
* the engine uses pi's automatic model resolution. */
|
* the engine uses pi's automatic model resolution. */
|
||||||
defaultProvider?: string;
|
defaultProvider?: string;
|
||||||
|
/** Optional credential instance for `defaultProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
defaultCredentialInstanceId?: string;
|
||||||
/** Default AI model ID within the provider (e.g. `"claude-sonnet-4-5"`).
|
/** Default AI model ID within the provider (e.g. `"claude-sonnet-4-5"`).
|
||||||
* Must be set together with `defaultProvider`. When both are undefined,
|
* Must be set together with `defaultProvider`. When both are undefined,
|
||||||
* the engine uses pi's automatic model resolution. */
|
* the engine uses pi's automatic model resolution. */
|
||||||
@@ -406,6 +408,8 @@ export interface GlobalSettings {
|
|||||||
* transient provider-side issues such as rate limits or overloaded capacity.
|
* transient provider-side issues such as rate limits or overloaded capacity.
|
||||||
* Must be set together with `fallbackModelId`. */
|
* Must be set together with `fallbackModelId`. */
|
||||||
fallbackProvider?: string;
|
fallbackProvider?: string;
|
||||||
|
/** Optional credential instance for `fallbackProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
fallbackCredentialInstanceId?: string;
|
||||||
/** Fallback AI model ID used with `fallbackProvider` when the primary default
|
/** Fallback AI model ID used with `fallbackProvider` when the primary default
|
||||||
* model fails due to transient provider-side issues such as rate limits or
|
* model fails due to transient provider-side issues such as rate limits or
|
||||||
* overloaded capacity. Must be set together with `fallbackProvider`. */
|
* overloaded capacity. Must be set together with `fallbackProvider`. */
|
||||||
@@ -683,6 +687,8 @@ export interface GlobalSettings {
|
|||||||
* Must be set together with `executionGlobalModelId`. Falls back to
|
* Must be set together with `executionGlobalModelId`. Falls back to
|
||||||
* `defaultProvider`/`defaultModelId` when undefined. */
|
* `defaultProvider`/`defaultModelId` when undefined. */
|
||||||
executionGlobalProvider?: string;
|
executionGlobalProvider?: string;
|
||||||
|
/** Optional credential instance for `executionGlobalProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
executionGlobalCredentialInstanceId?: string;
|
||||||
/** Global baseline AI model ID for task execution.
|
/** Global baseline AI model ID for task execution.
|
||||||
* Must be set together with `executionGlobalProvider`. */
|
* Must be set together with `executionGlobalProvider`. */
|
||||||
executionGlobalModelId?: string;
|
executionGlobalModelId?: string;
|
||||||
@@ -691,6 +697,8 @@ export interface GlobalSettings {
|
|||||||
* Must be set together with `planningGlobalModelId`. Falls back to
|
* Must be set together with `planningGlobalModelId`. Falls back to
|
||||||
* `defaultProvider`/`defaultModelId` when undefined. */
|
* `defaultProvider`/`defaultModelId` when undefined. */
|
||||||
planningGlobalProvider?: string;
|
planningGlobalProvider?: string;
|
||||||
|
/** Optional credential instance for `planningGlobalProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
planningGlobalCredentialInstanceId?: string;
|
||||||
/** Global baseline AI model ID for planning/triage.
|
/** Global baseline AI model ID for planning/triage.
|
||||||
* Must be set together with `planningGlobalProvider`. */
|
* Must be set together with `planningGlobalProvider`. */
|
||||||
planningGlobalModelId?: string;
|
planningGlobalModelId?: string;
|
||||||
@@ -699,6 +707,8 @@ export interface GlobalSettings {
|
|||||||
* Must be set together with `validatorGlobalModelId`. Falls back to
|
* Must be set together with `validatorGlobalModelId`. Falls back to
|
||||||
* `defaultProvider`/`defaultModelId` when undefined. */
|
* `defaultProvider`/`defaultModelId` when undefined. */
|
||||||
validatorGlobalProvider?: string;
|
validatorGlobalProvider?: string;
|
||||||
|
/** Optional credential instance for `validatorGlobalProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
validatorGlobalCredentialInstanceId?: string;
|
||||||
/** Global baseline AI model ID for validator/reviewer.
|
/** Global baseline AI model ID for validator/reviewer.
|
||||||
* Must be set together with `validatorGlobalProvider`. */
|
* Must be set together with `validatorGlobalProvider`. */
|
||||||
validatorGlobalModelId?: string;
|
validatorGlobalModelId?: string;
|
||||||
@@ -707,6 +717,8 @@ export interface GlobalSettings {
|
|||||||
* Must be set together with `titleSummarizerGlobalModelId`. Falls back to
|
* Must be set together with `titleSummarizerGlobalModelId`. Falls back to
|
||||||
* `defaultProvider`/`defaultModelId` when undefined. */
|
* `defaultProvider`/`defaultModelId` when undefined. */
|
||||||
titleSummarizerGlobalProvider?: string;
|
titleSummarizerGlobalProvider?: string;
|
||||||
|
/** Optional credential instance for `titleSummarizerGlobalProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
titleSummarizerGlobalCredentialInstanceId?: string;
|
||||||
/** Global baseline AI model ID for title summarization.
|
/** Global baseline AI model ID for title summarization.
|
||||||
* Must be set together with `titleSummarizerGlobalProvider`. */
|
* Must be set together with `titleSummarizerGlobalProvider`. */
|
||||||
titleSummarizerGlobalModelId?: string;
|
titleSummarizerGlobalModelId?: string;
|
||||||
@@ -718,6 +730,8 @@ export interface GlobalSettings {
|
|||||||
* Must be set together with `mergerGlobalModelId`. Falls back to
|
* Must be set together with `mergerGlobalModelId`. Falls back to
|
||||||
* `defaultProvider`/`defaultModelId` when undefined. */
|
* `defaultProvider`/`defaultModelId` when undefined. */
|
||||||
mergerGlobalProvider?: string;
|
mergerGlobalProvider?: string;
|
||||||
|
/** Optional credential instance for `mergerGlobalProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
mergerGlobalCredentialInstanceId?: string;
|
||||||
/** Global baseline AI model ID for merger agent sessions.
|
/** Global baseline AI model ID for merger agent sessions.
|
||||||
* Must be set together with `mergerGlobalProvider`. */
|
* Must be set together with `mergerGlobalProvider`. */
|
||||||
mergerGlobalModelId?: string;
|
mergerGlobalModelId?: string;
|
||||||
@@ -729,6 +743,8 @@ export interface GlobalSettings {
|
|||||||
* Must be set together with `importTranslateGlobalModelId`. Falls back to the
|
* Must be set together with `importTranslateGlobalModelId`. Falls back to the
|
||||||
* summarization lane, then `defaultProvider`/`defaultModelId`. */
|
* summarization lane, then `defaultProvider`/`defaultModelId`. */
|
||||||
importTranslateGlobalProvider?: string;
|
importTranslateGlobalProvider?: string;
|
||||||
|
/** Optional credential instance for `importTranslateGlobalProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
importTranslateGlobalCredentialInstanceId?: string;
|
||||||
/** Global baseline AI model ID for import auto-translation.
|
/** Global baseline AI model ID for import auto-translation.
|
||||||
* Must be set together with `importTranslateGlobalProvider`. */
|
* Must be set together with `importTranslateGlobalProvider`. */
|
||||||
importTranslateGlobalModelId?: string;
|
importTranslateGlobalModelId?: string;
|
||||||
@@ -1452,6 +1468,8 @@ export interface ProjectSettings {
|
|||||||
* Must be set together with `planningModelId`. When both are undefined,
|
* Must be set together with `planningModelId`. When both are undefined,
|
||||||
* falls back to `defaultProvider`/`defaultModelId`. */
|
* falls back to `defaultProvider`/`defaultModelId`. */
|
||||||
planningProvider?: string;
|
planningProvider?: string;
|
||||||
|
/** Optional credential instance for `planningProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
planningCredentialInstanceId?: string;
|
||||||
/** AI model ID for planning/triage (specification) agent.
|
/** AI model ID for planning/triage (specification) agent.
|
||||||
* Must be set together with `planningProvider`. When both are undefined,
|
* Must be set together with `planningProvider`. When both are undefined,
|
||||||
* falls back to `defaultProvider`/`defaultModelId`. */
|
* falls back to `defaultProvider`/`defaultModelId`. */
|
||||||
@@ -1459,6 +1477,8 @@ export interface ProjectSettings {
|
|||||||
/** Fallback model provider for planning/triage. When unset, falls back to the
|
/** Fallback model provider for planning/triage. When unset, falls back to the
|
||||||
* global fallback model. Must be set together with `planningFallbackModelId`. */
|
* global fallback model. Must be set together with `planningFallbackModelId`. */
|
||||||
planningFallbackProvider?: string;
|
planningFallbackProvider?: string;
|
||||||
|
/** Optional credential instance for `planningFallbackProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
planningFallbackCredentialInstanceId?: string;
|
||||||
/** Fallback model ID for planning/triage. When unset, falls back to the
|
/** Fallback model ID for planning/triage. When unset, falls back to the
|
||||||
* global fallback model. Must be set together with `planningFallbackProvider`. */
|
* global fallback model. Must be set together with `planningFallbackProvider`. */
|
||||||
planningFallbackModelId?: string;
|
planningFallbackModelId?: string;
|
||||||
@@ -1469,6 +1489,8 @@ export interface ProjectSettings {
|
|||||||
* for all lanes that don't have their own explicit project override.
|
* for all lanes that don't have their own explicit project override.
|
||||||
* Must be set together with `defaultModelIdOverride`. */
|
* Must be set together with `defaultModelIdOverride`. */
|
||||||
defaultProviderOverride?: string;
|
defaultProviderOverride?: string;
|
||||||
|
/** Optional credential instance for `defaultProviderOverride`; persisted but inert until runtime resolution. */
|
||||||
|
defaultCredentialInstanceIdOverride?: string;
|
||||||
/** Project-level override for the base default AI model ID.
|
/** Project-level override for the base default AI model ID.
|
||||||
* Must be set together with `defaultProviderOverride`. */
|
* Must be set together with `defaultProviderOverride`. */
|
||||||
defaultModelIdOverride?: string;
|
defaultModelIdOverride?: string;
|
||||||
@@ -1500,6 +1522,8 @@ export interface ProjectSettings {
|
|||||||
* `defaultProviderOverride`/`defaultModelIdOverride` or
|
* `defaultProviderOverride`/`defaultModelIdOverride` or
|
||||||
* `defaultProvider`/`defaultModelId` when undefined. */
|
* `defaultProvider`/`defaultModelId` when undefined. */
|
||||||
executionProvider?: string;
|
executionProvider?: string;
|
||||||
|
/** Optional credential instance for `executionProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
executionCredentialInstanceId?: string;
|
||||||
/** Project-level AI model ID for task execution.
|
/** Project-level AI model ID for task execution.
|
||||||
* Must be set together with `executionProvider`. */
|
* Must be set together with `executionProvider`. */
|
||||||
executionModelId?: string;
|
executionModelId?: string;
|
||||||
@@ -1512,6 +1536,8 @@ export interface ProjectSettings {
|
|||||||
*/
|
*/
|
||||||
/** Workflow fallback provider for executor sessions. Must pair with `executionFallbackModelId`; resolves before the shared global fallback pair. */
|
/** Workflow fallback provider for executor sessions. Must pair with `executionFallbackModelId`; resolves before the shared global fallback pair. */
|
||||||
executionFallbackProvider?: string;
|
executionFallbackProvider?: string;
|
||||||
|
/** Optional credential instance for `executionFallbackProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
executionFallbackCredentialInstanceId?: string;
|
||||||
/** Workflow fallback model ID for executor sessions. Must pair with `executionFallbackProvider`; resolves before the shared global fallback pair. */
|
/** Workflow fallback model ID for executor sessions. Must pair with `executionFallbackProvider`; resolves before the shared global fallback pair. */
|
||||||
executionFallbackModelId?: string;
|
executionFallbackModelId?: string;
|
||||||
/** Workflow executor-fallback thinking override. Inherits shared fallback thinking, then executor primary thinking. */
|
/** Workflow executor-fallback thinking override. Inherits shared fallback thinking, then executor primary thinking. */
|
||||||
@@ -1522,6 +1548,8 @@ export interface ProjectSettings {
|
|||||||
* Must be set together with `validatorModelId`. When both are undefined,
|
* Must be set together with `validatorModelId`. When both are undefined,
|
||||||
* falls back to `defaultProvider`/`defaultModelId`. */
|
* falls back to `defaultProvider`/`defaultModelId`. */
|
||||||
validatorProvider?: string;
|
validatorProvider?: string;
|
||||||
|
/** Optional credential instance for `validatorProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
validatorCredentialInstanceId?: string;
|
||||||
/** AI model ID for validator/reviewer agent.
|
/** AI model ID for validator/reviewer agent.
|
||||||
* Must be set together with `validatorProvider`. When both are undefined,
|
* Must be set together with `validatorProvider`. When both are undefined,
|
||||||
* falls back to `defaultProvider`/`defaultModelId`. */
|
* falls back to `defaultProvider`/`defaultModelId`. */
|
||||||
@@ -1530,6 +1558,8 @@ export interface ProjectSettings {
|
|||||||
* the global fallback model. Must be set together with
|
* the global fallback model. Must be set together with
|
||||||
* `validatorFallbackModelId`. */
|
* `validatorFallbackModelId`. */
|
||||||
validatorFallbackProvider?: string;
|
validatorFallbackProvider?: string;
|
||||||
|
/** Optional credential instance for `validatorFallbackProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
validatorFallbackCredentialInstanceId?: string;
|
||||||
/** Fallback model ID for validator/reviewer. When unset, falls back to the
|
/** Fallback model ID for validator/reviewer. When unset, falls back to the
|
||||||
* global fallback model. Must be set together with `validatorFallbackProvider`. */
|
* global fallback model. Must be set together with `validatorFallbackProvider`. */
|
||||||
validatorFallbackModelId?: string;
|
validatorFallbackModelId?: string;
|
||||||
@@ -2068,6 +2098,8 @@ export interface ProjectSettings {
|
|||||||
* Must be set together with `titleSummarizerModelId`. Falls back to planningProvider,
|
* Must be set together with `titleSummarizerModelId`. Falls back to planningProvider,
|
||||||
* then defaultProvider if not specified. */
|
* then defaultProvider if not specified. */
|
||||||
titleSummarizerProvider?: string;
|
titleSummarizerProvider?: string;
|
||||||
|
/** Optional credential instance for `titleSummarizerProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
titleSummarizerCredentialInstanceId?: string;
|
||||||
/** AI model ID for title summarization (when autoSummarizeTitles is enabled).
|
/** AI model ID for title summarization (when autoSummarizeTitles is enabled).
|
||||||
* Must be set together with `titleSummarizerProvider`. Falls back to planningModelId,
|
* Must be set together with `titleSummarizerProvider`. Falls back to planningModelId,
|
||||||
* then defaultModelId if not specified. */
|
* then defaultModelId if not specified. */
|
||||||
@@ -2082,6 +2114,8 @@ export interface ProjectSettings {
|
|||||||
* Must be set together with `mergerModelId`. Falls back to
|
* Must be set together with `mergerModelId`. Falls back to
|
||||||
* `mergerGlobalProvider`/`mergerGlobalModelId`, then project/global default. */
|
* `mergerGlobalProvider`/`mergerGlobalModelId`, then project/global default. */
|
||||||
mergerProvider?: string;
|
mergerProvider?: string;
|
||||||
|
/** Optional credential instance for `mergerProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
mergerCredentialInstanceId?: string;
|
||||||
/** Project AI model ID for merger agent sessions.
|
/** Project AI model ID for merger agent sessions.
|
||||||
* Must be set together with `mergerProvider`. */
|
* Must be set together with `mergerProvider`. */
|
||||||
mergerModelId?: string;
|
mergerModelId?: string;
|
||||||
@@ -2095,6 +2129,8 @@ export interface ProjectSettings {
|
|||||||
* Must be set together with `mergerFallbackModelId`. Resolves before the global
|
* Must be set together with `mergerFallbackModelId`. Resolves before the global
|
||||||
* `fallbackProvider`/`fallbackModelId` pair. */
|
* `fallbackProvider`/`fallbackModelId` pair. */
|
||||||
mergerFallbackProvider?: string;
|
mergerFallbackProvider?: string;
|
||||||
|
/** Optional credential instance for `mergerFallbackProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
mergerFallbackCredentialInstanceId?: string;
|
||||||
/** Project fallback AI model ID for merger agent sessions.
|
/** Project fallback AI model ID for merger agent sessions.
|
||||||
* Must be set together with `mergerFallbackProvider`. */
|
* Must be set together with `mergerFallbackProvider`. */
|
||||||
mergerFallbackModelId?: string;
|
mergerFallbackModelId?: string;
|
||||||
@@ -2110,6 +2146,8 @@ export interface ProjectSettings {
|
|||||||
* `importTranslateGlobalProvider`/`importTranslateGlobalModelId`, then the
|
* `importTranslateGlobalProvider`/`importTranslateGlobalModelId`, then the
|
||||||
* summarization lane, then project/global default. */
|
* summarization lane, then project/global default. */
|
||||||
importTranslateProvider?: string;
|
importTranslateProvider?: string;
|
||||||
|
/** Optional credential instance for `importTranslateProvider`. Persisted but inert until runtime credential resolution. */
|
||||||
|
importTranslateCredentialInstanceId?: string;
|
||||||
/** Project AI model ID for import auto-translation.
|
/** Project AI model ID for import auto-translation.
|
||||||
* Must be set together with `importTranslateProvider`. */
|
* Must be set together with `importTranslateProvider`. */
|
||||||
importTranslateModelId?: string;
|
importTranslateModelId?: string;
|
||||||
@@ -2130,6 +2168,8 @@ export interface ProjectSettings {
|
|||||||
* planning fallback, then global fallback. Must be set together with
|
* planning fallback, then global fallback. Must be set together with
|
||||||
* `titleSummarizerFallbackModelId`. */
|
* `titleSummarizerFallbackModelId`. */
|
||||||
titleSummarizerFallbackProvider?: string;
|
titleSummarizerFallbackProvider?: string;
|
||||||
|
/** Optional credential instance for `titleSummarizerFallbackProvider`; persisted but inert until runtime credential resolution. */
|
||||||
|
titleSummarizerFallbackCredentialInstanceId?: string;
|
||||||
/** Fallback model ID for title summarization. When unset, falls back to
|
/** Fallback model ID for title summarization. When unset, falls back to
|
||||||
* planning fallback, then global fallback. Must be set together with
|
* planning fallback, then global fallback. Must be set together with
|
||||||
* `titleSummarizerFallbackProvider`. */
|
* `titleSummarizerFallbackProvider`. */
|
||||||
|
|||||||
@@ -227,6 +227,8 @@ export interface CentralClaimStore {
|
|||||||
export interface TaskTokenUsagePerModel {
|
export interface TaskTokenUsagePerModel {
|
||||||
/** Provider of the actually-used model for this bucket. */
|
/** Provider of the actually-used model for this bucket. */
|
||||||
modelProvider?: string;
|
modelProvider?: string;
|
||||||
|
/** Optional credential instance for `modelProvider`; persisted but inert until runtime resolution. */
|
||||||
|
credentialInstanceId?: string;
|
||||||
/** Id of the actually-used model for this bucket. */
|
/** Id of the actually-used model for this bucket. */
|
||||||
modelId?: string;
|
modelId?: string;
|
||||||
/** Cumulative prompt/input tokens consumed by this model for the task. */
|
/** Cumulative prompt/input tokens consumed by this model for the task. */
|
||||||
@@ -272,6 +274,8 @@ export interface TaskTokenUsage {
|
|||||||
* Snapshot the provider of the actually-used model for analytics only. This is intentionally distinct from task.modelProvider, which is an own-model override used by model resolution and must not be written by token bookkeeping.
|
* Snapshot the provider of the actually-used model for analytics only. This is intentionally distinct from task.modelProvider, which is an own-model override used by model resolution and must not be written by token bookkeeping.
|
||||||
*/
|
*/
|
||||||
modelProvider?: string;
|
modelProvider?: string;
|
||||||
|
/** Optional credential instance for `modelProvider`; persisted but inert until runtime resolution. */
|
||||||
|
credentialInstanceId?: string;
|
||||||
/**
|
/**
|
||||||
* FNXC:TokenAnalytics 2026-06-18-16:23:
|
* FNXC:TokenAnalytics 2026-06-18-16:23:
|
||||||
* Snapshot the id of the actually-used model for analytics only. This is intentionally distinct from task.modelId, which is an own-model override used by model resolution and must not be written by token bookkeeping.
|
* Snapshot the id of the actually-used model for analytics only. This is intentionally distinct from task.modelId, which is an own-model override used by model resolution and must not be written by token bookkeeping.
|
||||||
@@ -790,10 +794,17 @@ export interface Task {
|
|||||||
reviewLevel?: number;
|
reviewLevel?: number;
|
||||||
/** Model preset selected during task creation. Presets resolve to concrete model overrides at creation time. */
|
/** Model preset selected during task creation. Presets resolve to concrete model overrides at creation time. */
|
||||||
modelPresetId?: string;
|
modelPresetId?: string;
|
||||||
|
/*
|
||||||
|
* FNXC:CredentialInstanceSelection 2026-08-01-05:43:
|
||||||
|
* Task model overrides persist optional credential-instance ids alongside their provider/model
|
||||||
|
* pairs. This slice stores and resolves the value only; runtime credential use is unchanged.
|
||||||
|
*/
|
||||||
/** AI model provider override for the executor agent (e.g., "anthropic").
|
/** AI model provider override for the executor agent (e.g., "anthropic").
|
||||||
* Must be set together with `modelId`. When both model fields are undefined,
|
* Must be set together with `modelId`. When both model fields are undefined,
|
||||||
* the executor uses global settings defaults. */
|
* the executor uses global settings defaults. */
|
||||||
modelProvider?: string;
|
modelProvider?: string;
|
||||||
|
/** Optional credential instance for `modelProvider`; persisted but inert until runtime resolution. */
|
||||||
|
credentialInstanceId?: string;
|
||||||
/** AI model ID override for the executor agent (e.g., "claude-sonnet-4-5").
|
/** AI model ID override for the executor agent (e.g., "claude-sonnet-4-5").
|
||||||
* Must be set together with `modelProvider`. When both model fields are undefined,
|
* Must be set together with `modelProvider`. When both model fields are undefined,
|
||||||
* the executor uses global settings defaults. */
|
* the executor uses global settings defaults. */
|
||||||
@@ -802,6 +813,8 @@ export interface Task {
|
|||||||
* Must be set together with `validatorModelId`. When both validator model fields
|
* Must be set together with `validatorModelId`. When both validator model fields
|
||||||
* are undefined, the reviewer uses global settings defaults. */
|
* are undefined, the reviewer uses global settings defaults. */
|
||||||
validatorModelProvider?: string;
|
validatorModelProvider?: string;
|
||||||
|
/** Optional credential instance for `validatorModelProvider`; persisted but inert until runtime resolution. */
|
||||||
|
validatorCredentialInstanceId?: string;
|
||||||
/** AI model ID override for the validator/reviewer agent.
|
/** AI model ID override for the validator/reviewer agent.
|
||||||
* Must be set together with `validatorModelProvider`. When both validator model
|
* Must be set together with `validatorModelProvider`. When both validator model
|
||||||
* fields are undefined, the reviewer uses global settings defaults. */
|
* fields are undefined, the reviewer uses global settings defaults. */
|
||||||
@@ -810,6 +823,8 @@ export interface Task {
|
|||||||
* Must be set together with `planningModelId`. When both planning model fields
|
* Must be set together with `planningModelId`. When both planning model fields
|
||||||
* are undefined, the triage agent uses global settings defaults. */
|
* are undefined, the triage agent uses global settings defaults. */
|
||||||
planningModelProvider?: string;
|
planningModelProvider?: string;
|
||||||
|
/** Optional credential instance for `planningModelProvider`; persisted but inert until runtime resolution. */
|
||||||
|
planningCredentialInstanceId?: string;
|
||||||
/** AI model ID override for the planning/triage agent.
|
/** AI model ID override for the planning/triage agent.
|
||||||
* Must be set together with `planningModelProvider`. When both planning model
|
* Must be set together with `planningModelProvider`. When both planning model
|
||||||
* fields are undefined, the triage agent uses global settings defaults. */
|
* fields are undefined, the triage agent uses global settings defaults. */
|
||||||
@@ -819,6 +834,8 @@ export interface Task {
|
|||||||
* Per-task merger overrides take precedence over the project/global merger lane only when both fields are set; merger sessions otherwise retain their existing settings-based resolution.
|
* Per-task merger overrides take precedence over the project/global merger lane only when both fields are set; merger sessions otherwise retain their existing settings-based resolution.
|
||||||
*/
|
*/
|
||||||
mergerModelProvider?: string;
|
mergerModelProvider?: string;
|
||||||
|
/** Optional credential instance for `mergerModelProvider`; persisted but inert until runtime resolution. */
|
||||||
|
mergerCredentialInstanceId?: string;
|
||||||
/** Must be set together with `mergerModelProvider`. */
|
/** Must be set together with `mergerModelProvider`. */
|
||||||
mergerModelId?: string;
|
mergerModelId?: string;
|
||||||
/** IDs of workflow steps enabled for this task, run after implementation completes */
|
/** IDs of workflow steps enabled for this task, run after implementation completes */
|
||||||
@@ -1354,10 +1371,17 @@ export interface TaskCreateInput {
|
|||||||
workflowId?: string | null;
|
workflowId?: string | null;
|
||||||
/** Model preset selected during task creation. Presets resolve to concrete model overrides at creation time. */
|
/** Model preset selected during task creation. Presets resolve to concrete model overrides at creation time. */
|
||||||
modelPresetId?: string;
|
modelPresetId?: string;
|
||||||
|
/*
|
||||||
|
* FNXC:CredentialInstanceSelection 2026-08-01-05:43:
|
||||||
|
* Task model overrides persist optional credential-instance ids alongside their provider/model
|
||||||
|
* pairs. This slice stores and resolves the value only; runtime credential use is unchanged.
|
||||||
|
*/
|
||||||
/** AI model provider override for the executor agent (e.g., "anthropic").
|
/** AI model provider override for the executor agent (e.g., "anthropic").
|
||||||
* Must be set together with `modelId`. When both model fields are undefined,
|
* Must be set together with `modelId`. When both model fields are undefined,
|
||||||
* the executor uses global settings defaults. */
|
* the executor uses global settings defaults. */
|
||||||
modelProvider?: string;
|
modelProvider?: string;
|
||||||
|
/** Optional credential instance for `modelProvider`; persisted but inert until runtime resolution. */
|
||||||
|
credentialInstanceId?: string;
|
||||||
/** AI model ID override for the executor agent (e.g., "claude-sonnet-4-5").
|
/** AI model ID override for the executor agent (e.g., "claude-sonnet-4-5").
|
||||||
* Must be set together with `modelProvider`. When both model fields are undefined,
|
* Must be set together with `modelProvider`. When both model fields are undefined,
|
||||||
* the executor uses global settings defaults. */
|
* the executor uses global settings defaults. */
|
||||||
@@ -1366,6 +1390,8 @@ export interface TaskCreateInput {
|
|||||||
* Must be set together with `validatorModelId`. When both validator model fields
|
* Must be set together with `validatorModelId`. When both validator model fields
|
||||||
* are undefined, the reviewer uses global settings defaults. */
|
* are undefined, the reviewer uses global settings defaults. */
|
||||||
validatorModelProvider?: string;
|
validatorModelProvider?: string;
|
||||||
|
/** Optional credential instance for `validatorModelProvider`; persisted but inert until runtime resolution. */
|
||||||
|
validatorCredentialInstanceId?: string;
|
||||||
/** AI model ID override for the validator/reviewer agent.
|
/** AI model ID override for the validator/reviewer agent.
|
||||||
* Must be set together with `validatorModelProvider`. When both validator model
|
* Must be set together with `validatorModelProvider`. When both validator model
|
||||||
* fields are undefined, the reviewer uses global settings defaults. */
|
* fields are undefined, the reviewer uses global settings defaults. */
|
||||||
@@ -1374,12 +1400,16 @@ export interface TaskCreateInput {
|
|||||||
* Must be set together with `planningModelId`. When both planning model fields
|
* Must be set together with `planningModelId`. When both planning model fields
|
||||||
* are undefined, the triage agent uses global settings defaults. */
|
* are undefined, the triage agent uses global settings defaults. */
|
||||||
planningModelProvider?: string;
|
planningModelProvider?: string;
|
||||||
|
/** Optional credential instance for `planningModelProvider`; persisted but inert until runtime resolution. */
|
||||||
|
planningCredentialInstanceId?: string;
|
||||||
/** AI model ID override for the planning/triage agent.
|
/** AI model ID override for the planning/triage agent.
|
||||||
* Must be set together with `planningModelProvider`. When both planning model
|
* Must be set together with `planningModelProvider`. When both planning model
|
||||||
* fields are undefined, the triage agent uses global settings defaults. */
|
* fields are undefined, the triage agent uses global settings defaults. */
|
||||||
planningModelId?: string;
|
planningModelId?: string;
|
||||||
/** Per-task merger override; provider and model id must be supplied together. */
|
/** Per-task merger override; provider and model id must be supplied together. */
|
||||||
mergerModelProvider?: string;
|
mergerModelProvider?: string;
|
||||||
|
/** Optional credential instance for `mergerModelProvider`; persisted but inert until runtime resolution. */
|
||||||
|
mergerCredentialInstanceId?: string;
|
||||||
mergerModelId?: string;
|
mergerModelId?: string;
|
||||||
/** Thinking level for AI agent sessions — controls reasoning effort (off/minimal/low/medium/high) */
|
/** Thinking level for AI agent sessions — controls reasoning effort (off/minimal/low/medium/high) */
|
||||||
thinkingLevel?: ThinkingLevel;
|
thinkingLevel?: ThinkingLevel;
|
||||||
|
|||||||
@@ -7,13 +7,22 @@
|
|||||||
|
|
||||||
import type { ThinkingLevel } from "./board.js";
|
import type { ThinkingLevel } from "./board.js";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FNXC:CredentialInstanceSelection 2026-08-01-05:38:
|
||||||
|
* Presets retain optional credential instances beside their model pairs. This data slice does
|
||||||
|
* not consume them at runtime, preserving existing selection behavior.
|
||||||
|
*/
|
||||||
export interface ModelPreset {
|
export interface ModelPreset {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
executorProvider?: string;
|
executorProvider?: string;
|
||||||
executorModelId?: string;
|
executorModelId?: string;
|
||||||
|
/** Optional credential instance for the executor pair; persisted but inert until runtime resolution. */
|
||||||
|
executorCredentialInstanceId?: string;
|
||||||
validatorProvider?: string;
|
validatorProvider?: string;
|
||||||
validatorModelId?: string;
|
validatorModelId?: string;
|
||||||
|
/** Optional credential instance for the validator pair; persisted but inert until runtime resolution. */
|
||||||
|
validatorCredentialInstanceId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A reusable workflow step definition that can run after task implementation. */
|
/** A reusable workflow step definition that can run after task implementation. */
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { getWorkflowExtensionRegistry } from "./workflow-extension-registry.js";
|
|||||||
import type { WorkflowExtensionConfigField } from "./workflow-extension-types.js";
|
import type { WorkflowExtensionConfigField } from "./workflow-extension-types.js";
|
||||||
import { THINKING_LEVELS } from "./types.js";
|
import { THINKING_LEVELS } from "./types.js";
|
||||||
import { resolveColumnFlags } from "./trait-registry.js";
|
import { resolveColumnFlags } from "./trait-registry.js";
|
||||||
|
import { isValidProviderInstanceId } from "./provider-instance.js";
|
||||||
// Side-effect import: registers the built-in traits so `resolveColumnFlags`
|
// Side-effect import: registers the built-in traits so `resolveColumnFlags`
|
||||||
// resolves the built-in `merge-blocker`/`intake` flags during save-time
|
// resolves the built-in `merge-blocker`/`intake` flags during save-time
|
||||||
// validation (U2). Custom/plugin traits that set the same flags resolve too.
|
// validation (U2). Custom/plugin traits that set the same flags resolve too.
|
||||||
@@ -940,6 +941,22 @@ function validateStepReviewRouting(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FNXC:CredentialInstanceSelection 2026-08-01-05:43:
|
||||||
|
* Workflow model overrides may persist an optional credential-instance id but this data-only slice
|
||||||
|
* never consumes it at runtime. Validate authoring input recursively before the graph can persist.
|
||||||
|
*/
|
||||||
|
function validateCredentialInstanceIdConfig(nodes: WorkflowIrNode[]): void {
|
||||||
|
for (const node of nodes) {
|
||||||
|
const value = node.config?.credentialInstanceId;
|
||||||
|
if (value !== undefined && (typeof value !== "string" || !isValidProviderInstanceId(value))) {
|
||||||
|
throw new WorkflowIrError(`Workflow node '${node.id}' credentialInstanceId must be a valid string when present`);
|
||||||
|
}
|
||||||
|
const templateNodes = (node.config as { template?: { nodes?: unknown } } | undefined)?.template?.nodes;
|
||||||
|
if (Array.isArray(templateNodes)) validateCredentialInstanceIdConfig(templateNodes as WorkflowIrNode[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function validateThinkingLevelConfig(nodes: WorkflowIrNode[]): void {
|
function validateThinkingLevelConfig(nodes: WorkflowIrNode[]): void {
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
const value = node.config?.thinkingLevel;
|
const value = node.config?.thinkingLevel;
|
||||||
@@ -1701,6 +1718,7 @@ function validateV2(ir: WorkflowIrV2): void {
|
|||||||
const topLevelIds = new Set(ir.nodes.map((n) => n.id));
|
const topLevelIds = new Set(ir.nodes.map((n) => n.id));
|
||||||
validateStepExecutePlacement(ir.nodes);
|
validateStepExecutePlacement(ir.nodes);
|
||||||
validateThinkingLevelConfig(ir.nodes);
|
validateThinkingLevelConfig(ir.nodes);
|
||||||
|
validateCredentialInstanceIdConfig(ir.nodes);
|
||||||
for (const node of ir.nodes) {
|
for (const node of ir.nodes) {
|
||||||
if (node.kind === "foreach") validateForeach(node, topLevelIds, columnIds);
|
if (node.kind === "foreach") validateForeach(node, topLevelIds, columnIds);
|
||||||
if (node.kind === "loop") validateLoop(node, topLevelIds, columnIds);
|
if (node.kind === "loop") validateLoop(node, topLevelIds, columnIds);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
import type {
|
import type {
|
||||||
WorkflowSettingDefinition,
|
WorkflowSettingDefinition,
|
||||||
} from "./workflow-ir-types.js";
|
} from "./workflow-ir-types.js";
|
||||||
|
import {isValidProviderInstanceId} from "./provider-instance.js";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Typed rejection (TransitionRejection-style: flat, JSON-safe, no class)
|
// Typed rejection (TransitionRejection-style: flat, JSON-safe, no class)
|
||||||
@@ -252,6 +253,15 @@ export function validateSettingValuePatch(
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
* FNXC:CredentialInstanceSelection 2026-08-01-05:38:
|
||||||
|
* Workflow-declared credential companions are persisted but inert in this slice; reject malformed
|
||||||
|
* ids at this write boundary so resolution never needs runtime validation.
|
||||||
|
*/
|
||||||
|
if (key.endsWith("CredentialInstanceId") && !isValidProviderInstanceId(value)) {
|
||||||
|
rejections.push(makeWorkflowSettingRejection("type-mismatch", key, `setting '${key}' must be a valid credential instance id`));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const res = validateValue(setting, value);
|
const res = validateValue(setting, value);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
rejections.push(res.rejection);
|
rejections.push(res.rejection);
|
||||||
|
|||||||
Reference in New Issue
Block a user