@cat-factory/app 0.194.0 → 0.195.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/components/pipeline/AgentPromptEditor.vue +40 -0
- package/app/components/pipeline/OutputBudgetInput.vue +69 -0
- package/app/components/pipeline/PipelineBuilder.vue +42 -0
- package/app/composables/api/agentSettings.ts +32 -0
- package/app/composables/useApi.ts +2 -0
- package/app/stores/agentSettings.ts +86 -0
- package/app/stores/outputBudget.spec.ts +55 -0
- package/app/stores/pipelines/draftStepConfig.ts +23 -0
- package/app/types/agent-settings.ts +12 -0
- package/i18n/locales/de.json +9 -1
- package/i18n/locales/en.json +9 -1
- package/i18n/locales/es.json +9 -1
- package/i18n/locales/fr.json +9 -1
- package/i18n/locales/he.json +9 -1
- package/i18n/locales/it.json +9 -1
- package/i18n/locales/ja.json +9 -1
- package/i18n/locales/pl.json +9 -1
- package/i18n/locales/tr.json +9 -1
- package/i18n/locales/uk.json +9 -1
- package/package.json +2 -2
|
@@ -3,6 +3,7 @@ import { computed, ref, watch } from 'vue'
|
|
|
3
3
|
import type { AgentPromptRevision } from '~/types/agent-prompts'
|
|
4
4
|
import { agentKindMeta } from '~/utils/catalog'
|
|
5
5
|
import AgentKindIcon from '~/components/pipeline/AgentKindIcon.vue'
|
|
6
|
+
import OutputBudgetInput from '~/components/pipeline/OutputBudgetInput.vue'
|
|
6
7
|
import {
|
|
7
8
|
draftForRevision,
|
|
8
9
|
isDirty,
|
|
@@ -30,6 +31,7 @@ const emit = defineEmits<{ close: [] }>()
|
|
|
30
31
|
const { t } = useI18n()
|
|
31
32
|
const toast = useToast()
|
|
32
33
|
const prompts = useAgentPromptsStore()
|
|
34
|
+
const agentSettings = useAgentSettingsStore()
|
|
33
35
|
|
|
34
36
|
const open = computed({
|
|
35
37
|
get: () => props.agentKind !== null,
|
|
@@ -76,6 +78,29 @@ watch(
|
|
|
76
78
|
const detail = computed(() => prompts.detail)
|
|
77
79
|
const label = computed(() => (props.agentKind ? agentKindMeta(props.agentKind).label : ''))
|
|
78
80
|
|
|
81
|
+
/**
|
|
82
|
+
* The workspace-wide output-token ceiling for this kind — the same per-agent-kind scope this
|
|
83
|
+
* editor already owns for the prompt, which is why it lives here rather than in a settings screen
|
|
84
|
+
* of its own. A pipeline step may still pin its own budget over it.
|
|
85
|
+
*
|
|
86
|
+
* Saved on its own, immediately: unlike the prompt (whose save appends a revision and wants an
|
|
87
|
+
* explicit commit) this is one scalar with no history, so a separate Save button would only invite
|
|
88
|
+
* someone to type a number, close the modal and wonder why nothing changed.
|
|
89
|
+
*/
|
|
90
|
+
const budget = computed(() =>
|
|
91
|
+
props.agentKind ? agentSettings.maxOutputTokensFor(props.agentKind) : undefined,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
async function saveBudget(value: number | null) {
|
|
95
|
+
const kind = props.agentKind
|
|
96
|
+
if (!kind) return
|
|
97
|
+
try {
|
|
98
|
+
await agentSettings.setMaxOutputTokens(kind, value)
|
|
99
|
+
} catch {
|
|
100
|
+
toast.add({ title: t('agentPrompt.toast.budgetFailed'), color: 'error' })
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
79
104
|
/** The live prompt, so "no change" can be reported instead of appending an identical revision. */
|
|
80
105
|
const dirty = computed(() => isDirty(draft.value, detail.value))
|
|
81
106
|
/** Nothing to revert to when the workspace is already running the shipped prompt. */
|
|
@@ -164,6 +189,21 @@ function revisionLabel(revision: AgentPromptRevision): string {
|
|
|
164
189
|
</UBadge>
|
|
165
190
|
</div>
|
|
166
191
|
|
|
192
|
+
<!-- The workspace-wide output ceiling for this kind. Same per-agent-kind scope as the
|
|
193
|
+
prompt below it; saves on change, since there is no revision log to commit to. -->
|
|
194
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
195
|
+
<span class="text-[11px] text-slate-400">{{ t('pipeline.outputBudget.kindLabel') }}</span>
|
|
196
|
+
<OutputBudgetInput
|
|
197
|
+
class="w-32"
|
|
198
|
+
:model-value="budget"
|
|
199
|
+
:disabled="agentSettings.saving"
|
|
200
|
+
@update:model-value="saveBudget"
|
|
201
|
+
/>
|
|
202
|
+
<span class="text-[10px] text-slate-500">
|
|
203
|
+
{{ t('pipeline.outputBudget.kindHint') }}
|
|
204
|
+
</span>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
167
207
|
<!-- What the platform appends is SHOWN, not described. A prose summary of it is copy
|
|
168
208
|
that silently goes stale the moment a directive is added, and a user who does not
|
|
169
209
|
know what is already there writes a prompt that fights it. -->
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The output-token ceiling control, shared by both tiers that configure one: a pipeline step's
|
|
4
|
+
* own override (in the builder) and a workspace's per-agent-kind default (in the prompt editor).
|
|
5
|
+
*
|
|
6
|
+
* Empty means INHERIT, and that is the whole reason this is a component rather than a bare
|
|
7
|
+
* `UInput type="number"` at each call site: "no value" has to round-trip as `null` and never as
|
|
8
|
+
* `0`, or clearing the field would send a zero-token ceiling — every reply empty — instead of
|
|
9
|
+
* falling back to the next tier. The bounds come from the contract, so the input cannot offer a
|
|
10
|
+
* value the server would reject.
|
|
11
|
+
*/
|
|
12
|
+
import { computed } from 'vue'
|
|
13
|
+
import { MAX_AGENT_MAX_OUTPUT_TOKENS, MIN_AGENT_MAX_OUTPUT_TOKENS } from '~/types/agent-settings'
|
|
14
|
+
|
|
15
|
+
const props = defineProps<{
|
|
16
|
+
/** The configured ceiling, or null/undefined when this tier inherits. */
|
|
17
|
+
modelValue: number | null | undefined
|
|
18
|
+
/** What this tier falls back to, shown as the placeholder so "inherit" names a number. */
|
|
19
|
+
inheritedValue?: number | undefined
|
|
20
|
+
disabled?: boolean
|
|
21
|
+
}>()
|
|
22
|
+
|
|
23
|
+
const emit = defineEmits<{ 'update:modelValue': [number | null] }>()
|
|
24
|
+
|
|
25
|
+
const { t, n } = useI18n()
|
|
26
|
+
|
|
27
|
+
/** Bound to the input as a string so an empty field is distinguishable from a typed 0. */
|
|
28
|
+
const text = computed(() => (props.modelValue != null ? String(props.modelValue) : ''))
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The placeholder names the inherited ceiling when the caller knows it, so a user reading an
|
|
32
|
+
* empty field learns what the step will actually run on rather than just that it is unset.
|
|
33
|
+
*/
|
|
34
|
+
const placeholder = computed(() =>
|
|
35
|
+
props.inheritedValue != null
|
|
36
|
+
? t('pipeline.outputBudget.inheritsValue', { tokens: n(props.inheritedValue) })
|
|
37
|
+
: t('pipeline.outputBudget.inherits'),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Commit on change. An empty/unparseable field clears to `null` (inherit); anything else is
|
|
42
|
+
* clamped into the contract's range rather than rejected, so a fat-fingered extra digit lands on
|
|
43
|
+
* the ceiling instead of silently failing the save with a 422.
|
|
44
|
+
*/
|
|
45
|
+
function commit(raw: string | number) {
|
|
46
|
+
const trimmed = String(raw).trim()
|
|
47
|
+
if (trimmed === '') return emit('update:modelValue', null)
|
|
48
|
+
const parsed = Number.parseInt(trimmed, 10)
|
|
49
|
+
if (!Number.isFinite(parsed)) return emit('update:modelValue', null)
|
|
50
|
+
const clamped = Math.min(
|
|
51
|
+
MAX_AGENT_MAX_OUTPUT_TOKENS,
|
|
52
|
+
Math.max(MIN_AGENT_MAX_OUTPUT_TOKENS, parsed),
|
|
53
|
+
)
|
|
54
|
+
emit('update:modelValue', clamped)
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<UInput
|
|
60
|
+
:model-value="text"
|
|
61
|
+
type="number"
|
|
62
|
+
size="xs"
|
|
63
|
+
:min="MIN_AGENT_MAX_OUTPUT_TOKENS"
|
|
64
|
+
:max="MAX_AGENT_MAX_OUTPUT_TOKENS"
|
|
65
|
+
:placeholder="placeholder"
|
|
66
|
+
:disabled="disabled"
|
|
67
|
+
@change="commit(($event.target as HTMLInputElement).value)"
|
|
68
|
+
/>
|
|
69
|
+
</template>
|
|
@@ -5,6 +5,7 @@ import type { AgentKind, Pipeline, PipelinePurpose } from '~/types/domain'
|
|
|
5
5
|
import AgentPalette from '~/components/palettes/AgentPalette.vue'
|
|
6
6
|
import AgentKindIcon from '~/components/pipeline/AgentKindIcon.vue'
|
|
7
7
|
import AgentPromptEditor from '~/components/pipeline/AgentPromptEditor.vue'
|
|
8
|
+
import OutputBudgetInput from '~/components/pipeline/OutputBudgetInput.vue'
|
|
8
9
|
import { showOverrideField } from '~/utils/uiMode'
|
|
9
10
|
import {
|
|
10
11
|
agentKindMeta,
|
|
@@ -86,6 +87,7 @@ const agents = useAgentsStore()
|
|
|
86
87
|
const ui = useUiStore()
|
|
87
88
|
const uiMode = useUiModeStore()
|
|
88
89
|
const agentPrompts = useAgentPromptsStore()
|
|
90
|
+
const agentSettings = useAgentSettingsStore()
|
|
89
91
|
|
|
90
92
|
// The agent kind whose system prompt is open in the editor (null = closed). Per-KIND, not
|
|
91
93
|
// per-step: an override applies to every run of that agent in the workspace, so two steps of
|
|
@@ -102,6 +104,26 @@ const promptEditorKind = ref<AgentKind | null>(null)
|
|
|
102
104
|
function showPromptEditor(kind: AgentKind): boolean {
|
|
103
105
|
return showOverrideField(uiMode.isAdvanced, agentPrompts.isCustomized(kind) || null)
|
|
104
106
|
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Whether this step's own output-budget field shows. Same override rule as the prompt editor: a
|
|
110
|
+
* step with no pinned ceiling inherits, so the control is advanced-only until a value actually
|
|
111
|
+
* exists — at which point it must be visible in BOTH tiers, or a basic-mode user runs on a budget
|
|
112
|
+
* a teammate pinned and they can neither see nor clear.
|
|
113
|
+
*/
|
|
114
|
+
function showOutputBudget(index: number): boolean {
|
|
115
|
+
return showOverrideField(uiMode.isAdvanced, pipelines.draftMaxOutputTokens(index) ?? null)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* What a step with no pinned ceiling actually runs on: the workspace's per-kind setting when it
|
|
120
|
+
* has one. Shown as the field's placeholder. Undefined ⇒ the deployment default, which the SPA
|
|
121
|
+
* deliberately does NOT guess at — it is env-resolved per agent kind and a number invented here
|
|
122
|
+
* would be wrong on the deployments that tuned it.
|
|
123
|
+
*/
|
|
124
|
+
function inheritedOutputBudget(kind: AgentKind): number | undefined {
|
|
125
|
+
return agentSettings.maxOutputTokensFor(kind)
|
|
126
|
+
}
|
|
105
127
|
const releaseHealth = useReleaseHealthStore()
|
|
106
128
|
const skills = useSkillsStore()
|
|
107
129
|
|
|
@@ -151,6 +173,9 @@ watch(open, (isOpen) => {
|
|
|
151
173
|
// Best-effort: the builder is fully usable without it, and a deployment that wires no
|
|
152
174
|
// override store answers 503 here.
|
|
153
175
|
if (isOpen) agentPrompts.loadIndex().catch(() => {})
|
|
176
|
+
// The workspace's per-kind output ceilings, which the per-step field shows as its inherited
|
|
177
|
+
// placeholder and the prompt editor edits. Best-effort on the same terms as the prompt index.
|
|
178
|
+
if (isOpen) agentSettings.load().catch(() => {})
|
|
154
179
|
})
|
|
155
180
|
|
|
156
181
|
function add(kind: AgentKind) {
|
|
@@ -694,6 +719,23 @@ async function clone(p: Pipeline) {
|
|
|
694
719
|
</p>
|
|
695
720
|
</div>
|
|
696
721
|
|
|
722
|
+
<!-- This step's own output-token ceiling. An OVERRIDE of the workspace's per-kind
|
|
723
|
+
setting (itself an override of the deployment routing default), so it is
|
|
724
|
+
advanced-only until a value is pinned; empty inherits. -->
|
|
725
|
+
<div v-if="showOutputBudget(unit.index)" class="ms-6 flex items-center gap-2">
|
|
726
|
+
<span class="text-[10px] text-slate-500">
|
|
727
|
+
{{ t('pipeline.outputBudget.stepLabel') }}
|
|
728
|
+
</span>
|
|
729
|
+
<OutputBudgetInput
|
|
730
|
+
class="w-28"
|
|
731
|
+
:model-value="pipelines.draftMaxOutputTokens(unit.index)"
|
|
732
|
+
:inherited-value="inheritedOutputBudget(unit.kind)"
|
|
733
|
+
@update:model-value="
|
|
734
|
+
pipelines.setDraftMaxOutputTokens(unit.index, $event ?? undefined)
|
|
735
|
+
"
|
|
736
|
+
/>
|
|
737
|
+
</div>
|
|
738
|
+
|
|
697
739
|
<!-- Attached companion: a dependent reviewer for this producer, optionally
|
|
698
740
|
gated on the task estimate. -->
|
|
699
741
|
<div
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
listWorkspaceAgentSettingsContract,
|
|
3
|
+
updateWorkspaceAgentSettingsContract,
|
|
4
|
+
} from '@cat-factory/contracts'
|
|
5
|
+
import type { UpdateWorkspaceAgentSettingsInput } from '~/types/agent-settings'
|
|
6
|
+
import type { ApiContext } from './context'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The workspace's per-agent-kind generation settings, edited from the pipeline builder. There is
|
|
10
|
+
* no delete route: clearing the last configured field IS the way back to the deployment default,
|
|
11
|
+
* and the server drops the row when nothing is left set.
|
|
12
|
+
*/
|
|
13
|
+
export function agentSettingsApi({ send, ws }: ApiContext) {
|
|
14
|
+
return {
|
|
15
|
+
// Every kind the workspace has configured — nothing for a kind that inherits.
|
|
16
|
+
listWorkspaceAgentSettings: (workspaceId: string) =>
|
|
17
|
+
send(listWorkspaceAgentSettingsContract, { pathPrefix: ws(workspaceId) }),
|
|
18
|
+
|
|
19
|
+
// Patch one kind. An explicit null clears the field; the response is null once the kind is
|
|
20
|
+
// back to inheriting entirely.
|
|
21
|
+
updateWorkspaceAgentSettings: (
|
|
22
|
+
workspaceId: string,
|
|
23
|
+
agentKind: string,
|
|
24
|
+
body: UpdateWorkspaceAgentSettingsInput,
|
|
25
|
+
) =>
|
|
26
|
+
send(updateWorkspaceAgentSettingsContract, {
|
|
27
|
+
pathPrefix: ws(workspaceId),
|
|
28
|
+
pathParams: { agentKind },
|
|
29
|
+
body,
|
|
30
|
+
}),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -3,6 +3,7 @@ import { createApiClient, createSend, createSendWith } from './api/client'
|
|
|
3
3
|
import type { ApiContext } from './api/context'
|
|
4
4
|
import { accountsApi } from './api/accounts'
|
|
5
5
|
import { agentPromptsApi } from './api/agentPrompts'
|
|
6
|
+
import { agentSettingsApi } from './api/agentSettings'
|
|
6
7
|
import { platformObservabilityApi } from './api/platformObservability'
|
|
7
8
|
import { reportsApi } from './api/reports'
|
|
8
9
|
import { authApi } from './api/auth'
|
|
@@ -136,6 +137,7 @@ export function useApi() {
|
|
|
136
137
|
...notificationsApi(ctx),
|
|
137
138
|
...presetsApi(ctx),
|
|
138
139
|
...agentPromptsApi(ctx),
|
|
140
|
+
...agentSettingsApi(ctx),
|
|
139
141
|
...preflightsApi(ctx),
|
|
140
142
|
...publicApiKeysApi(ctx),
|
|
141
143
|
...sharedStacksApi(ctx),
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
import type { WorkspaceAgentSettings } from '~/types/agent-settings'
|
|
4
|
+
import { useWorkspaceStore } from '~/stores/workspace'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The workspace's per-agent-kind generation settings — the pipeline builder's output-budget
|
|
8
|
+
* control, sitting beside the prompt editor.
|
|
9
|
+
*
|
|
10
|
+
* Unlike the prompt store there is only ONE shape to load: the whole configured set is a handful
|
|
11
|
+
* of small rows (a kind that inherits has no row at all), so the index IS the detail and the
|
|
12
|
+
* builder can badge every step and populate its editor from one request. There is nothing here
|
|
13
|
+
* worth deferring the way a prompt body is.
|
|
14
|
+
*/
|
|
15
|
+
export const useAgentSettingsStore = defineStore('agentSettings', () => {
|
|
16
|
+
const api = useApi()
|
|
17
|
+
|
|
18
|
+
const settings = ref<WorkspaceAgentSettings[]>([])
|
|
19
|
+
const loading = ref(false)
|
|
20
|
+
const saving = ref(false)
|
|
21
|
+
|
|
22
|
+
/** Configured ceilings by agent kind, for O(1) lookup while rendering a pipeline's steps. */
|
|
23
|
+
const ceilingByKind = computed(() => {
|
|
24
|
+
const out = new Map<string, number>()
|
|
25
|
+
for (const row of settings.value) {
|
|
26
|
+
if (row.maxOutputTokens != null) out.set(row.agentKind, row.maxOutputTokens)
|
|
27
|
+
}
|
|
28
|
+
return out
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
/** This kind's configured ceiling, or undefined when it inherits the deployment default. */
|
|
32
|
+
function maxOutputTokensFor(agentKind: string): number | undefined {
|
|
33
|
+
return ceilingByKind.value.get(agentKind)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Load the configured set. Best-effort like the prompt index: the builder is fully usable
|
|
38
|
+
* without it (every kind simply runs the deployment ceiling), and the endpoint 503s on a
|
|
39
|
+
* deployment that wires no settings store at all.
|
|
40
|
+
*/
|
|
41
|
+
async function load() {
|
|
42
|
+
const ws = useWorkspaceStore()
|
|
43
|
+
if (!ws.workspaceId) return
|
|
44
|
+
loading.value = true
|
|
45
|
+
try {
|
|
46
|
+
settings.value = await api.listWorkspaceAgentSettings(ws.requireId())
|
|
47
|
+
} finally {
|
|
48
|
+
loading.value = false
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Set (or clear, with `null`) one kind's output-token ceiling.
|
|
54
|
+
*
|
|
55
|
+
* Reconciles from the row the SERVER returned rather than the value sent: it answers `null`
|
|
56
|
+
* once the kind is back to inheriting, which is the same signal the row should disappear —
|
|
57
|
+
* so a clear and a set both land through one code path and the store can never keep a row the
|
|
58
|
+
* server has dropped.
|
|
59
|
+
*/
|
|
60
|
+
async function setMaxOutputTokens(agentKind: string, maxOutputTokens: number | null) {
|
|
61
|
+
const ws = useWorkspaceStore()
|
|
62
|
+
saving.value = true
|
|
63
|
+
try {
|
|
64
|
+
const updated = await api.updateWorkspaceAgentSettings(ws.requireId(), agentKind, {
|
|
65
|
+
maxOutputTokens,
|
|
66
|
+
})
|
|
67
|
+
const rest = settings.value.filter((s) => s.agentKind !== agentKind)
|
|
68
|
+
settings.value = updated
|
|
69
|
+
? [...rest, updated].sort((a, b) => a.agentKind.localeCompare(b.agentKind))
|
|
70
|
+
: rest
|
|
71
|
+
return updated
|
|
72
|
+
} finally {
|
|
73
|
+
saving.value = false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
settings,
|
|
79
|
+
loading,
|
|
80
|
+
saving,
|
|
81
|
+
ceilingByKind,
|
|
82
|
+
maxOutputTokensFor,
|
|
83
|
+
load,
|
|
84
|
+
setMaxOutputTokens,
|
|
85
|
+
}
|
|
86
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { usePipelinesStore } from '~/stores/pipelines'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The per-step output-token ceiling rides the shared `StepOptions` bag, so its helpers owe the
|
|
6
|
+
* same normalization every other field in there follows: merge rather than clobber, and drop the
|
|
7
|
+
* whole entry once the bag empties. That last part is what keeps an all-default pipeline from
|
|
8
|
+
* persisting a `step_options` array of empty objects.
|
|
9
|
+
*/
|
|
10
|
+
describe('pipelines store — per-step output budget', () => {
|
|
11
|
+
it('sets, reads and clears a draft step’s ceiling', () => {
|
|
12
|
+
const pipelines = usePipelinesStore()
|
|
13
|
+
pipelines.addToDraft('doc-researcher')
|
|
14
|
+
expect(pipelines.draftMaxOutputTokens(0)).toBeUndefined()
|
|
15
|
+
|
|
16
|
+
pipelines.setDraftMaxOutputTokens(0, 24_000)
|
|
17
|
+
expect(pipelines.draftMaxOutputTokens(0)).toBe(24_000)
|
|
18
|
+
expect(pipelines.draftStepOptions[0]).toEqual({ maxOutputTokens: 24_000 })
|
|
19
|
+
|
|
20
|
+
// Clearing drops the field and, with the bag now empty, normalizes the entry back to null —
|
|
21
|
+
// so a step back on the inherited budget persists no options at all.
|
|
22
|
+
pipelines.setDraftMaxOutputTokens(0, undefined)
|
|
23
|
+
expect(pipelines.draftMaxOutputTokens(0)).toBeUndefined()
|
|
24
|
+
expect(pipelines.draftStepOptions[0]).toBeNull()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('merges with the other options on the step rather than clobbering the bag', () => {
|
|
28
|
+
const pipelines = usePipelinesStore()
|
|
29
|
+
pipelines.addToDraft('requirements-review')
|
|
30
|
+
pipelines.toggleDraftAutoRecommend(0)
|
|
31
|
+
expect(pipelines.draftStepOptions[0]).toEqual({ autoRecommend: false })
|
|
32
|
+
|
|
33
|
+
pipelines.setDraftMaxOutputTokens(0, 12_000)
|
|
34
|
+
expect(pipelines.draftStepOptions[0]).toEqual({
|
|
35
|
+
autoRecommend: false,
|
|
36
|
+
maxOutputTokens: 12_000,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// And clearing one field leaves the other standing (the entry stays, since the bag is not empty).
|
|
40
|
+
pipelines.setDraftMaxOutputTokens(0, undefined)
|
|
41
|
+
expect(pipelines.draftStepOptions[0]).toEqual({ autoRecommend: false })
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('keeps each step’s ceiling independent', () => {
|
|
45
|
+
const pipelines = usePipelinesStore()
|
|
46
|
+
pipelines.addToDraft('doc-researcher')
|
|
47
|
+
pipelines.addToDraft('doc-outliner')
|
|
48
|
+
|
|
49
|
+
pipelines.setDraftMaxOutputTokens(0, 24_000)
|
|
50
|
+
pipelines.setDraftMaxOutputTokens(1, 10_000)
|
|
51
|
+
|
|
52
|
+
expect(pipelines.draftMaxOutputTokens(0)).toBe(24_000)
|
|
53
|
+
expect(pipelines.draftMaxOutputTokens(1)).toBe(10_000)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
@@ -139,6 +139,27 @@ export function createPipelineStepConfigActions(ctx: PipelinesContext) {
|
|
|
139
139
|
draftStepOptions.value[index] = Object.keys(next).length ? next : null
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* The output-token ceiling pinned on the draft step at `index`, or undefined when the step
|
|
144
|
+
* inherits (the workspace's per-kind setting, else the deployment default).
|
|
145
|
+
*/
|
|
146
|
+
function draftMaxOutputTokens(index: number): number | undefined {
|
|
147
|
+
return draftStepOptions.value[index]?.maxOutputTokens
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Set (or clear) this step's own output-token ceiling. Merges into the step's `StepOptions`
|
|
152
|
+
* bag rather than clobbering it; clearing drops the field and, if the bag empties, the whole
|
|
153
|
+
* entry — so a step back on the inherited budget persists no options at all, exactly like the
|
|
154
|
+
* other fields here.
|
|
155
|
+
*/
|
|
156
|
+
function setDraftMaxOutputTokens(index: number, maxOutputTokens: number | undefined) {
|
|
157
|
+
const next: StepOptions = { ...draftStepOptions.value[index] }
|
|
158
|
+
if (maxOutputTokens != null) next.maxOutputTokens = maxOutputTokens
|
|
159
|
+
else delete next.maxOutputTokens
|
|
160
|
+
draftStepOptions.value[index] = Object.keys(next).length ? next : null
|
|
161
|
+
}
|
|
162
|
+
|
|
142
163
|
return {
|
|
143
164
|
toggleDraftGating,
|
|
144
165
|
toggleDraftConsensus,
|
|
@@ -153,5 +174,7 @@ export function createPipelineStepConfigActions(ctx: PipelinesContext) {
|
|
|
153
174
|
toggleDraftAutoRecommend,
|
|
154
175
|
draftSkillId,
|
|
155
176
|
setDraftSkillId,
|
|
177
|
+
draftMaxOutputTokens,
|
|
178
|
+
setDraftMaxOutputTokens,
|
|
156
179
|
}
|
|
157
180
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Per-workspace, per-agent-kind generation settings, mirroring `@cat-factory/contracts`
|
|
2
|
+
// (agent-settings.ts). Today one knob: the output-token ceiling a kind's inline calls run under.
|
|
3
|
+
// A kind absent from the store inherits the deployment routing default, and a pipeline step's own
|
|
4
|
+
// `stepOptions.maxOutputTokens` still overrides whatever is set here.
|
|
5
|
+
//
|
|
6
|
+
// All wire shapes are sourced from @cat-factory/contracts (single source of truth).
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
UpdateWorkspaceAgentSettingsInput,
|
|
10
|
+
WorkspaceAgentSettings,
|
|
11
|
+
} from '@cat-factory/contracts'
|
|
12
|
+
export { MAX_AGENT_MAX_OUTPUT_TOKENS, MIN_AGENT_MAX_OUTPUT_TOKENS } from '@cat-factory/contracts'
|
package/i18n/locales/de.json
CHANGED
|
@@ -3750,6 +3750,13 @@
|
|
|
3750
3750
|
"deleteFailed": "Pipeline konnte nicht gelöscht werden",
|
|
3751
3751
|
"removeFailed": "Pipeline konnte nicht entfernt werden"
|
|
3752
3752
|
}
|
|
3753
|
+
},
|
|
3754
|
+
"outputBudget": {
|
|
3755
|
+
"stepLabel": "Ausgabebudget",
|
|
3756
|
+
"kindLabel": "Ausgabebudget",
|
|
3757
|
+
"kindHint": "Gilt für jeden Lauf dieses Agenten. Ein Pipeline-Schritt kann es überschreiben.",
|
|
3758
|
+
"inherits": "Übernommen",
|
|
3759
|
+
"inheritsValue": "Übernommen ({tokens})"
|
|
3753
3760
|
}
|
|
3754
3761
|
},
|
|
3755
3762
|
"agentPrompt": {
|
|
@@ -3784,7 +3791,8 @@
|
|
|
3784
3791
|
"reverted": "Zurück zum Standardprompt",
|
|
3785
3792
|
"saveFailed": "Prompt konnte nicht gespeichert werden",
|
|
3786
3793
|
"loadFailed": "Prompt konnte nicht geladen werden",
|
|
3787
|
-
"conflict": "Jemand anderes hat diesen Prompt geändert. Neu geladen - wenden Sie Ihre Änderung erneut an."
|
|
3794
|
+
"conflict": "Jemand anderes hat diesen Prompt geändert. Neu geladen - wenden Sie Ihre Änderung erneut an.",
|
|
3795
|
+
"budgetFailed": "Ausgabebudget konnte nicht gespeichert werden"
|
|
3788
3796
|
}
|
|
3789
3797
|
},
|
|
3790
3798
|
"agentTier": {
|
package/i18n/locales/en.json
CHANGED
|
@@ -4188,6 +4188,13 @@
|
|
|
4188
4188
|
"deleteFailed": "Could not delete pipeline",
|
|
4189
4189
|
"removeFailed": "Could not remove pipeline"
|
|
4190
4190
|
}
|
|
4191
|
+
},
|
|
4192
|
+
"outputBudget": {
|
|
4193
|
+
"stepLabel": "Output budget",
|
|
4194
|
+
"kindLabel": "Output budget",
|
|
4195
|
+
"kindHint": "Applies to every run of this agent. A pipeline step can override it.",
|
|
4196
|
+
"inherits": "Inherited",
|
|
4197
|
+
"inheritsValue": "Inherited ({tokens})"
|
|
4191
4198
|
}
|
|
4192
4199
|
},
|
|
4193
4200
|
"agentPrompt": {
|
|
@@ -4231,7 +4238,8 @@
|
|
|
4231
4238
|
"reverted": "Back to the built-in prompt",
|
|
4232
4239
|
"saveFailed": "Couldn't save the prompt",
|
|
4233
4240
|
"loadFailed": "Couldn't load the prompt",
|
|
4234
|
-
"conflict": "Someone else changed this prompt. Reloaded - re-apply your edit."
|
|
4241
|
+
"conflict": "Someone else changed this prompt. Reloaded - re-apply your edit.",
|
|
4242
|
+
"budgetFailed": "Could not save the output budget"
|
|
4235
4243
|
}
|
|
4236
4244
|
},
|
|
4237
4245
|
"agentTier": {
|
package/i18n/locales/es.json
CHANGED
|
@@ -4073,6 +4073,13 @@
|
|
|
4073
4073
|
"deleteFailed": "No se pudo eliminar el pipeline",
|
|
4074
4074
|
"removeFailed": "No se pudo quitar el pipeline"
|
|
4075
4075
|
}
|
|
4076
|
+
},
|
|
4077
|
+
"outputBudget": {
|
|
4078
|
+
"stepLabel": "Presupuesto de salida",
|
|
4079
|
+
"kindLabel": "Presupuesto de salida",
|
|
4080
|
+
"kindHint": "Se aplica a todas las ejecuciones de este agente. Un paso del pipeline puede anularlo.",
|
|
4081
|
+
"inherits": "Heredado",
|
|
4082
|
+
"inheritsValue": "Heredado ({tokens})"
|
|
4076
4083
|
}
|
|
4077
4084
|
},
|
|
4078
4085
|
"agentPrompt": {
|
|
@@ -4107,7 +4114,8 @@
|
|
|
4107
4114
|
"reverted": "De vuelta al prompt predeterminado",
|
|
4108
4115
|
"saveFailed": "No se pudo guardar el prompt",
|
|
4109
4116
|
"loadFailed": "No se pudo cargar el prompt",
|
|
4110
|
-
"conflict": "Otra persona cambió este prompt. Se recargó: vuelve a aplicar tu edición."
|
|
4117
|
+
"conflict": "Otra persona cambió este prompt. Se recargó: vuelve a aplicar tu edición.",
|
|
4118
|
+
"budgetFailed": "No se pudo guardar el presupuesto de salida"
|
|
4111
4119
|
}
|
|
4112
4120
|
},
|
|
4113
4121
|
"agentTier": {
|
package/i18n/locales/fr.json
CHANGED
|
@@ -4073,6 +4073,13 @@
|
|
|
4073
4073
|
"deleteFailed": "Impossible de supprimer le pipeline",
|
|
4074
4074
|
"removeFailed": "Impossible de retirer le pipeline"
|
|
4075
4075
|
}
|
|
4076
|
+
},
|
|
4077
|
+
"outputBudget": {
|
|
4078
|
+
"stepLabel": "Budget de sortie",
|
|
4079
|
+
"kindLabel": "Budget de sortie",
|
|
4080
|
+
"kindHint": "S'applique à chaque exécution de cet agent. Une étape du pipeline peut le remplacer.",
|
|
4081
|
+
"inherits": "Hérité",
|
|
4082
|
+
"inheritsValue": "Hérité ({tokens})"
|
|
4076
4083
|
}
|
|
4077
4084
|
},
|
|
4078
4085
|
"agentPrompt": {
|
|
@@ -4107,7 +4114,8 @@
|
|
|
4107
4114
|
"reverted": "Retour à l'invite par défaut",
|
|
4108
4115
|
"saveFailed": "Impossible d'enregistrer l'invite",
|
|
4109
4116
|
"loadFailed": "Impossible de charger l'invite",
|
|
4110
|
-
"conflict": "Quelqu'un d'autre a modifié cette invite. Rechargée : réappliquez votre modification."
|
|
4117
|
+
"conflict": "Quelqu'un d'autre a modifié cette invite. Rechargée : réappliquez votre modification.",
|
|
4118
|
+
"budgetFailed": "Impossible d'enregistrer le budget de sortie"
|
|
4111
4119
|
}
|
|
4112
4120
|
},
|
|
4113
4121
|
"agentTier": {
|
package/i18n/locales/he.json
CHANGED
|
@@ -4084,6 +4084,13 @@
|
|
|
4084
4084
|
"deleteFailed": "לא ניתן למחוק את הצינור",
|
|
4085
4085
|
"removeFailed": "לא ניתן היה להסיר את הצינור"
|
|
4086
4086
|
}
|
|
4087
|
+
},
|
|
4088
|
+
"outputBudget": {
|
|
4089
|
+
"stepLabel": "תקציב פלט",
|
|
4090
|
+
"kindLabel": "תקציב פלט",
|
|
4091
|
+
"kindHint": "חל על כל הרצה של הסוכן הזה. שלב בצינור יכול לעקוף אותו.",
|
|
4092
|
+
"inherits": "בירושה",
|
|
4093
|
+
"inheritsValue": "בירושה ({tokens})"
|
|
4087
4094
|
}
|
|
4088
4095
|
},
|
|
4089
4096
|
"agentPrompt": {
|
|
@@ -4118,7 +4125,8 @@
|
|
|
4118
4125
|
"reverted": "חזרה להנחיה המובנית",
|
|
4119
4126
|
"saveFailed": "לא ניתן היה לשמור את ההנחיה",
|
|
4120
4127
|
"loadFailed": "לא ניתן היה לטעון את ההנחיה",
|
|
4121
|
-
"conflict": "מישהו אחר שינה את ההנחיה הזו. נטענה מחדש - החילו את העריכה שלכם שוב."
|
|
4128
|
+
"conflict": "מישהו אחר שינה את ההנחיה הזו. נטענה מחדש - החילו את העריכה שלכם שוב.",
|
|
4129
|
+
"budgetFailed": "לא ניתן היה לשמור את תקציב הפלט"
|
|
4122
4130
|
}
|
|
4123
4131
|
},
|
|
4124
4132
|
"agentTier": {
|
package/i18n/locales/it.json
CHANGED
|
@@ -3750,6 +3750,13 @@
|
|
|
3750
3750
|
"deleteFailed": "Impossibile eliminare la pipeline",
|
|
3751
3751
|
"removeFailed": "Impossibile rimuovere la pipeline"
|
|
3752
3752
|
}
|
|
3753
|
+
},
|
|
3754
|
+
"outputBudget": {
|
|
3755
|
+
"stepLabel": "Budget di output",
|
|
3756
|
+
"kindLabel": "Budget di output",
|
|
3757
|
+
"kindHint": "Si applica a ogni esecuzione di questo agente. Un passaggio della pipeline può sovrascriverlo.",
|
|
3758
|
+
"inherits": "Ereditato",
|
|
3759
|
+
"inheritsValue": "Ereditato ({tokens})"
|
|
3753
3760
|
}
|
|
3754
3761
|
},
|
|
3755
3762
|
"agentPrompt": {
|
|
@@ -3784,7 +3791,8 @@
|
|
|
3784
3791
|
"reverted": "Tornato al prompt predefinito",
|
|
3785
3792
|
"saveFailed": "Impossibile salvare il prompt",
|
|
3786
3793
|
"loadFailed": "Impossibile caricare il prompt",
|
|
3787
|
-
"conflict": "Qualcun altro ha modificato questo prompt. Ricaricato: riapplica la tua modifica."
|
|
3794
|
+
"conflict": "Qualcun altro ha modificato questo prompt. Ricaricato: riapplica la tua modifica.",
|
|
3795
|
+
"budgetFailed": "Impossibile salvare il budget di output"
|
|
3788
3796
|
}
|
|
3789
3797
|
},
|
|
3790
3798
|
"agentTier": {
|
package/i18n/locales/ja.json
CHANGED
|
@@ -4085,6 +4085,13 @@
|
|
|
4085
4085
|
"deleteFailed": "パイプラインを削除できませんでした",
|
|
4086
4086
|
"removeFailed": "パイプラインを削除できませんでした"
|
|
4087
4087
|
}
|
|
4088
|
+
},
|
|
4089
|
+
"outputBudget": {
|
|
4090
|
+
"stepLabel": "出力予算",
|
|
4091
|
+
"kindLabel": "出力予算",
|
|
4092
|
+
"kindHint": "このエージェントのすべての実行に適用されます。パイプラインのステップで上書きできます。",
|
|
4093
|
+
"inherits": "継承",
|
|
4094
|
+
"inheritsValue": "継承({tokens})"
|
|
4088
4095
|
}
|
|
4089
4096
|
},
|
|
4090
4097
|
"agentPrompt": {
|
|
@@ -4119,7 +4126,8 @@
|
|
|
4119
4126
|
"reverted": "組み込みのプロンプトに戻しました",
|
|
4120
4127
|
"saveFailed": "プロンプトを保存できませんでした",
|
|
4121
4128
|
"loadFailed": "プロンプトを読み込めませんでした",
|
|
4122
|
-
"conflict": "このプロンプトは別のユーザーが変更しました。再読み込みしました。編集をやり直してください。"
|
|
4129
|
+
"conflict": "このプロンプトは別のユーザーが変更しました。再読み込みしました。編集をやり直してください。",
|
|
4130
|
+
"budgetFailed": "出力予算を保存できませんでした"
|
|
4123
4131
|
}
|
|
4124
4132
|
},
|
|
4125
4133
|
"agentTier": {
|
package/i18n/locales/pl.json
CHANGED
|
@@ -4073,6 +4073,13 @@
|
|
|
4073
4073
|
"deleteFailed": "Nie udało się usunąć pipeline'u",
|
|
4074
4074
|
"removeFailed": "Nie udało się usunąć pipeline’u"
|
|
4075
4075
|
}
|
|
4076
|
+
},
|
|
4077
|
+
"outputBudget": {
|
|
4078
|
+
"stepLabel": "Budżet wyjścia",
|
|
4079
|
+
"kindLabel": "Budżet wyjścia",
|
|
4080
|
+
"kindHint": "Dotyczy każdego uruchomienia tego agenta. Krok pipeline'u może to nadpisać.",
|
|
4081
|
+
"inherits": "Odziedziczone",
|
|
4082
|
+
"inheritsValue": "Odziedziczone ({tokens})"
|
|
4076
4083
|
}
|
|
4077
4084
|
},
|
|
4078
4085
|
"agentPrompt": {
|
|
@@ -4107,7 +4114,8 @@
|
|
|
4107
4114
|
"reverted": "Powrót do wbudowanego promptu",
|
|
4108
4115
|
"saveFailed": "Nie udało się zapisać promptu",
|
|
4109
4116
|
"loadFailed": "Nie udało się wczytać promptu",
|
|
4110
|
-
"conflict": "Ktoś inny zmienił ten prompt. Wczytano ponownie - zastosuj swoją zmianę jeszcze raz."
|
|
4117
|
+
"conflict": "Ktoś inny zmienił ten prompt. Wczytano ponownie - zastosuj swoją zmianę jeszcze raz.",
|
|
4118
|
+
"budgetFailed": "Nie udało się zapisać budżetu wyjścia"
|
|
4111
4119
|
}
|
|
4112
4120
|
},
|
|
4113
4121
|
"agentTier": {
|
package/i18n/locales/tr.json
CHANGED
|
@@ -4085,6 +4085,13 @@
|
|
|
4085
4085
|
"deleteFailed": "Pipeline silinemedi",
|
|
4086
4086
|
"removeFailed": "Pipeline kaldırılamadı"
|
|
4087
4087
|
}
|
|
4088
|
+
},
|
|
4089
|
+
"outputBudget": {
|
|
4090
|
+
"stepLabel": "Çıktı bütçesi",
|
|
4091
|
+
"kindLabel": "Çıktı bütçesi",
|
|
4092
|
+
"kindHint": "Bu ajanın her çalışmasına uygulanır. Bir pipeline adımı bunu geçersiz kılabilir.",
|
|
4093
|
+
"inherits": "Devralındı",
|
|
4094
|
+
"inheritsValue": "Devralındı ({tokens})"
|
|
4088
4095
|
}
|
|
4089
4096
|
},
|
|
4090
4097
|
"agentPrompt": {
|
|
@@ -4119,7 +4126,8 @@
|
|
|
4119
4126
|
"reverted": "Yerleşik isteme dönüldü",
|
|
4120
4127
|
"saveFailed": "İstem kaydedilemedi",
|
|
4121
4128
|
"loadFailed": "İstem yüklenemedi",
|
|
4122
|
-
"conflict": "Bu istemi başka biri değiştirdi. Yeniden yüklendi - düzenlemenizi tekrar uygulayın."
|
|
4129
|
+
"conflict": "Bu istemi başka biri değiştirdi. Yeniden yüklendi - düzenlemenizi tekrar uygulayın.",
|
|
4130
|
+
"budgetFailed": "Çıktı bütçesi kaydedilemedi"
|
|
4123
4131
|
}
|
|
4124
4132
|
},
|
|
4125
4133
|
"agentTier": {
|
package/i18n/locales/uk.json
CHANGED
|
@@ -4073,6 +4073,13 @@
|
|
|
4073
4073
|
"deleteFailed": "Не вдалося видалити пайплайн",
|
|
4074
4074
|
"removeFailed": "Не вдалося вилучити пайплайн"
|
|
4075
4075
|
}
|
|
4076
|
+
},
|
|
4077
|
+
"outputBudget": {
|
|
4078
|
+
"stepLabel": "Бюджет виводу",
|
|
4079
|
+
"kindLabel": "Бюджет виводу",
|
|
4080
|
+
"kindHint": "Застосовується до кожного запуску цього агента. Крок пайплайну може це перевизначити.",
|
|
4081
|
+
"inherits": "Успадковано",
|
|
4082
|
+
"inheritsValue": "Успадковано ({tokens})"
|
|
4076
4083
|
}
|
|
4077
4084
|
},
|
|
4078
4085
|
"agentPrompt": {
|
|
@@ -4107,7 +4114,8 @@
|
|
|
4107
4114
|
"reverted": "Повернуто вбудований промпт",
|
|
4108
4115
|
"saveFailed": "Не вдалося зберегти промпт",
|
|
4109
4116
|
"loadFailed": "Не вдалося завантажити промпт",
|
|
4110
|
-
"conflict": "Цей промпт змінив хтось інший. Перезавантажено - застосуйте свою правку ще раз."
|
|
4117
|
+
"conflict": "Цей промпт змінив хтось інший. Перезавантажено - застосуйте свою правку ще раз.",
|
|
4118
|
+
"budgetFailed": "Не вдалося зберегти бюджет виводу"
|
|
4111
4119
|
}
|
|
4112
4120
|
},
|
|
4113
4121
|
"agentTier": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.195.0",
|
|
4
4
|
"description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"valibot": "^1.4.2",
|
|
41
41
|
"vue": "3.5.40",
|
|
42
42
|
"wretch": "^3.0.9",
|
|
43
|
-
"@cat-factory/contracts": "0.
|
|
43
|
+
"@cat-factory/contracts": "0.202.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@toad-contracts/testing": "0.3.2",
|