@cat-factory/app 0.241.0 → 0.241.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/app/components/panels/StepToolServers.logic.ts +8 -0
- package/app/stores/notifications.ts +40 -7
- package/app/stores/workspace/commands.ts +1 -1
- package/app/stores/workspace/hydrate.ts +12 -7
- package/app/stores/workspace.spec.ts +91 -4
- package/app/stores/workspace.ts +18 -13
- package/i18n/locales/de.json +3 -3
- package/i18n/locales/en.json +3 -3
- package/i18n/locales/es.json +3 -3
- package/i18n/locales/fr.json +3 -3
- package/i18n/locales/he.json +3 -3
- package/i18n/locales/it.json +3 -3
- package/i18n/locales/ja.json +3 -3
- package/i18n/locales/pl.json +3 -3
- package/i18n/locales/tr.json +3 -3
- package/i18n/locales/uk.json +3 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -618,7 +618,17 @@ event left to restore it.
|
|
|
618
618
|
- **Never gate readiness on a snapshot a later resync can undo.** The on-connect resync flips
|
|
619
619
|
`connected` only after it settles (which is why e2e gates on `data-connected`).
|
|
620
620
|
- **A REPLACE-style `hydrate` must never silently drop live-only state.** Either fold that state
|
|
621
|
-
into the snapshot or reconcile rather than replace.
|
|
621
|
+
into the snapshot or reconcile rather than replace. The `refreshSeq` guard above orders
|
|
622
|
+
refreshes against each OTHER and does nothing when ONE slow fetch straddles a live event, so
|
|
623
|
+
every such store also takes a WATERMARK: `refresh()` captures each one's `hydrateBaseline()`
|
|
624
|
+
before the fetch (`LiveWriteBaselines`) and its `hydrate` keeps whatever was written after it.
|
|
625
|
+
`board` and `notifications` are the two today; `execution` gets the same protection from the
|
|
626
|
+
server `rev` it carries. Whether the store can re-derive the dropped state is what decides how
|
|
627
|
+
bad the bug is: a block's status arrives again on the run's next transition, while a
|
|
628
|
+
notification is pushed ONCE, so dropping the card leaves a parked run nothing can surface (the
|
|
629
|
+
`pr-review` spec's flaky 30s wait on `notifications-bell`). A watermark that tracks only
|
|
630
|
+
INSERTS is half a guard: a card resolved live must also stay gone, or a snapshot read while it
|
|
631
|
+
was open resurrects an action the server has already taken.
|
|
622
632
|
- **An action's OPTIMISTIC ECHO is a clobber too, and it bypasses both guards above.** A store
|
|
623
633
|
that awaits a mutation and then assigns the returned sub-state onto the cached run
|
|
624
634
|
(`step.forkDecision`, `step.prReview`, `step.judge`, `step.followUps`) is writing straight past
|
|
@@ -37,6 +37,14 @@ export const REASON_KEY: Record<ToolServerUnavailableReason, string> = {
|
|
|
37
37
|
* (`docs`' table in `backend/docs/mcp-tool-servers.md` is the same mapping for a reader who never
|
|
38
38
|
* opens the SPA); a reason with no remedy leaves the operator holding an accurate diagnosis and no
|
|
39
39
|
* next step, which is the state this surface was built to end.
|
|
40
|
+
*
|
|
41
|
+
* EDITING ONE OF THESE MEANS READING EVERY CAUSE IT COVERS FIRST. A member is not a cause:
|
|
42
|
+
* `harness_unsupported`, `missing_secret` and `oauth_not_connected` are each reached from more
|
|
43
|
+
* than one place, and a line addressing only the obvious one is a dead end for whoever hit the
|
|
44
|
+
* other, which is worse than the bare diagnosis this replaced because it also costs them the
|
|
45
|
+
* attempt. The causes per member are enumerated on kernel's `UnavailableToolServer` (which the SPA
|
|
46
|
+
* cannot import) and restated in that doc's table, whose "What happened" column is the list to
|
|
47
|
+
* check a remedy against.
|
|
40
48
|
*/
|
|
41
49
|
export const REMEDY_KEY: Record<ToolServerUnavailableReason, string> = {
|
|
42
50
|
harness_unsupported: 'panels.stepDetail.toolServers.remedy.harnessUnsupported',
|
|
@@ -22,11 +22,42 @@ export const useNotificationsStore = defineStore('notifications', () => {
|
|
|
22
22
|
remove,
|
|
23
23
|
} = useUpsertList<Notification>({ key: (n) => n.id, prepend: true })
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
// Client-side monotonic guard against a stale full-snapshot `hydrate` CLOBBERING newer live
|
|
26
|
+
// state — the same hazard `useBoardStore` guards, on the delivery shape that has no second
|
|
27
|
+
// chance. A run raises its card as a targeted `notification` event; a full `refresh()` whose
|
|
28
|
+
// snapshot was READ before that card existed can resolve AFTER it, and a plain replace then
|
|
29
|
+
// drops the card with NO further event to restore it, so the inbox bell never appears (the
|
|
30
|
+
// pr-review e2e flake). Notifications carry no server revision, so each live write is stamped
|
|
31
|
+
// with a monotonic sequence and a refresh that captured its baseline BEFORE the fetch keeps
|
|
32
|
+
// every write newer than that baseline. It cuts both ways: a live-ADDED card the snapshot
|
|
33
|
+
// cannot know about is re-inserted, and a live-RESOLVED one the snapshot still calls open
|
|
34
|
+
// stays gone.
|
|
35
|
+
let liveSeq = 0
|
|
36
|
+
/** Last live write per id: the notification to keep, or `null` once it was resolved. */
|
|
37
|
+
const liveWrites = new Map<string, { seq: number; value: Notification | null }>()
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Baseline for {@link hydrate}: capture this BEFORE a refresh's snapshot fetch and pass it
|
|
41
|
+
* back in, so a notification written live while the fetch was in flight survives the hydrate.
|
|
42
|
+
* Callers that don't pass a baseline get a plain full replace (initial load / board switch —
|
|
43
|
+
* no live-write race to guard).
|
|
44
|
+
*/
|
|
45
|
+
function hydrateBaseline(): number {
|
|
46
|
+
return liveSeq
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Replace the cache from a server snapshot, keeping live writes newer than `since`. */
|
|
50
|
+
function hydrate(notifications: Notification[], since = liveSeq) {
|
|
51
|
+
const newer = new Map<string, Notification | null>()
|
|
52
|
+
for (const [id, write] of liveWrites) {
|
|
53
|
+
// A write the snapshot already reflects is reconciled and can be forgotten, so the map
|
|
54
|
+
// stays bounded by what is genuinely in flight rather than by the session's history.
|
|
55
|
+
if (write.seq > since) newer.set(id, write.value)
|
|
56
|
+
else liveWrites.delete(id)
|
|
57
|
+
}
|
|
58
|
+
const merged = notifications.filter((n) => n.status === 'open' && !newer.has(n.id))
|
|
59
|
+
for (const value of newer.values()) if (value) merged.push(value)
|
|
60
|
+
open.value = merged.sort((a, b) => b.createdAt - a.createdAt)
|
|
30
61
|
}
|
|
31
62
|
|
|
32
63
|
/**
|
|
@@ -34,7 +65,9 @@ export const useNotificationsStore = defineStore('notifications', () => {
|
|
|
34
65
|
* replaced in place; a resolved one (acted/dismissed) is removed from the inbox.
|
|
35
66
|
*/
|
|
36
67
|
function upsert(notification: Notification) {
|
|
37
|
-
|
|
68
|
+
const isOpen = notification.status === 'open'
|
|
69
|
+
liveWrites.set(notification.id, { seq: ++liveSeq, value: isOpen ? notification : null })
|
|
70
|
+
if (!isOpen) {
|
|
38
71
|
remove(notification.id)
|
|
39
72
|
return
|
|
40
73
|
}
|
|
@@ -76,5 +109,5 @@ export const useNotificationsStore = defineStore('notifications', () => {
|
|
|
76
109
|
upsert(resolved)
|
|
77
110
|
}
|
|
78
111
|
|
|
79
|
-
return { open, hydrate, upsert, byBlock, count, act, dismiss }
|
|
112
|
+
return { open, hydrate, hydrateBaseline, upsert, byBlock, count, act, dismiss }
|
|
80
113
|
})
|
|
@@ -13,7 +13,7 @@ export interface WorkspaceCommandContext {
|
|
|
13
13
|
api: ReturnType<typeof useApi>
|
|
14
14
|
workspaceId: Ref<string | null>
|
|
15
15
|
workspaces: Ref<WorkspaceListItem[]>
|
|
16
|
-
hydrate: (snapshot: WorkspaceSnapshot
|
|
16
|
+
hydrate: (snapshot: WorkspaceSnapshot) => void
|
|
17
17
|
/** Open one of the active account's boards, creating one when it has none. */
|
|
18
18
|
resolveActiveBoard: () => Promise<void>
|
|
19
19
|
}
|
|
@@ -49,23 +49,28 @@ export function resetPerBoardCaches() {
|
|
|
49
49
|
useFragmentsStore().invalidate()
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* The live-write watermarks a refresh captured BEFORE its snapshot fetch, one per store whose
|
|
54
|
+
* `hydrate` REPLACES a list that live events also write to. Each store preserves whatever it
|
|
55
|
+
* was handed after its own baseline, so a slower refresh can't clobber newer live state (see
|
|
56
|
+
* `useBoardStore().hydrate` and `useNotificationsStore().hydrate`). Omitted by fresh loads
|
|
57
|
+
* (init / board switch / create), where there is no in-flight race to guard.
|
|
58
|
+
*/
|
|
59
|
+
export type LiveWriteBaselines = { board: number; notifications: number }
|
|
60
|
+
|
|
52
61
|
/**
|
|
53
62
|
* Fan a workspace snapshot out into the per-feature data stores. Extracted verbatim from the
|
|
54
63
|
* `workspace` store's `hydrate` (which keeps the workspace-scoped state it owns + the
|
|
55
64
|
* board-switch cache reset) so the ordering of the hydrate calls is preserved exactly — a
|
|
56
65
|
* size-only split, not a new seam.
|
|
57
|
-
*
|
|
58
|
-
* `boardSince` (captured BEFORE this snapshot's fetch) lets the board store preserve any block
|
|
59
|
-
* live-`upsert`ed while the fetch was in flight, so a slower refresh can't clobber a newer live
|
|
60
|
-
* status (see `useBoardStore().hydrate`).
|
|
61
66
|
*/
|
|
62
|
-
export function applySnapshotToStores(snapshot: WorkspaceSnapshot,
|
|
67
|
+
export function applySnapshotToStores(snapshot: WorkspaceSnapshot, baselines?: LiveWriteBaselines) {
|
|
63
68
|
useUserSettingsStore().hydrate(snapshot.userSettings ?? null)
|
|
64
69
|
// The signed-in user's tutorial progress MERGES rather than replaces (see the store): both id
|
|
65
70
|
// lists are grow-only sets, so a snapshot must never un-say a walkthrough this browser finished
|
|
66
71
|
// while the mirror write was failing. Absent ⇒ no server copy, and the local one stands.
|
|
67
72
|
useTutorialStore().mergeServerProgress(snapshot.tutorialProgress ?? null)
|
|
68
|
-
useBoardStore().hydrate(snapshot.blocks,
|
|
73
|
+
useBoardStore().hydrate(snapshot.blocks, baselines?.board)
|
|
69
74
|
useBoardStore().hydrateArchived(snapshot.archivedServices ?? [])
|
|
70
75
|
usePipelinesStore().hydrate(
|
|
71
76
|
snapshot.pipelines,
|
|
@@ -77,7 +82,7 @@ export function applySnapshotToStores(snapshot: WorkspaceSnapshot, boardSince?:
|
|
|
77
82
|
useAgentRunsStore().hydrate(snapshot.bootstrapJobs ?? [], snapshot.workspace.id)
|
|
78
83
|
useAgentRunsStore().hydrateEnvConfigRepair(snapshot.envConfigRepairJobs ?? [])
|
|
79
84
|
useEnvironmentTestStore().hydrate(snapshot.environmentTestRuns ?? [], snapshot.workspace.id)
|
|
80
|
-
useNotificationsStore().hydrate(snapshot.notifications ?? [])
|
|
85
|
+
useNotificationsStore().hydrate(snapshot.notifications ?? [], baselines?.notifications)
|
|
81
86
|
useRiskPoliciesStore().hydrate(snapshot.riskPolicies ?? [], snapshot.riskPolicyCatalogVersions)
|
|
82
87
|
useSharedStacksStore().hydrate(snapshot.sharedStacks ?? [])
|
|
83
88
|
useWorkspaceSettingsStore().hydrate(snapshot.settings)
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
-
import type { Block, WorkspaceSnapshot } from '~/types/domain'
|
|
2
|
+
import type { Block, Notification, WorkspaceSnapshot } from '~/types/domain'
|
|
3
3
|
import { useBoardStore } from '~/stores/board'
|
|
4
|
+
import { useNotificationsStore } from '~/stores/notifications'
|
|
4
5
|
import { useWorkspaceStore } from '~/stores/workspace'
|
|
5
6
|
|
|
6
7
|
// The workspace store's `hydrate` fans out to ~20 sibling stores via Nuxt auto-imports, which
|
|
7
8
|
// aren't defined under plain vitest. Stub every one INERT (a proxy whose every method is a no-op)
|
|
8
|
-
// EXCEPT the
|
|
9
|
+
// EXCEPT the two stores whose live-write guards these tests drive: the board store (kept real via
|
|
10
|
+
// the stub below) and the notifications store (imported by name in `workspace/hydrate.ts`, so it
|
|
11
|
+
// is real already and must NOT be listed here).
|
|
9
12
|
const INERT_STORES = [
|
|
10
13
|
'useAccountsStore',
|
|
11
14
|
'useAgentConfigStore',
|
|
@@ -22,7 +25,6 @@ const INERT_STORES = [
|
|
|
22
25
|
'useInitiativesStore',
|
|
23
26
|
'useRiskPoliciesStore',
|
|
24
27
|
'useModelPresetsStore',
|
|
25
|
-
'useNotificationsStore',
|
|
26
28
|
'usePipelinesStore',
|
|
27
29
|
'useProviderConnectionsStore',
|
|
28
30
|
'useRecurringPipelinesStore',
|
|
@@ -68,13 +70,34 @@ function block(id: string, over: Partial<Block> = {}): Block {
|
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
/** Minimal open inbox card — only the fields the notifications store reads. */
|
|
74
|
+
function notification(id: string, over: Partial<Notification> = {}): Notification {
|
|
75
|
+
return {
|
|
76
|
+
id,
|
|
77
|
+
type: 'pr_review_ready',
|
|
78
|
+
status: 'open',
|
|
79
|
+
blockId: 't1',
|
|
80
|
+
executionId: null,
|
|
81
|
+
title: id,
|
|
82
|
+
body: '',
|
|
83
|
+
createdAt: 1,
|
|
84
|
+
resolvedAt: null,
|
|
85
|
+
...over,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
71
89
|
/** Minimal snapshot — the arrays a bare hydrate iterates; everything else defaults. */
|
|
72
|
-
function snapshot(
|
|
90
|
+
function snapshot(
|
|
91
|
+
id: string,
|
|
92
|
+
blocks: Block[],
|
|
93
|
+
notifications: Notification[] = [],
|
|
94
|
+
): WorkspaceSnapshot {
|
|
73
95
|
return {
|
|
74
96
|
workspace: { id, name: id, accountId: null },
|
|
75
97
|
blocks,
|
|
76
98
|
pipelines: [],
|
|
77
99
|
executions: [],
|
|
100
|
+
notifications,
|
|
78
101
|
} as unknown as WorkspaceSnapshot
|
|
79
102
|
}
|
|
80
103
|
|
|
@@ -149,6 +172,70 @@ describe('workspace store refresh ordering', () => {
|
|
|
149
172
|
// The live terminal status survives — the stale refresh did NOT clobber it back.
|
|
150
173
|
expect(board.getBlock('t1')?.status).toBe('done')
|
|
151
174
|
})
|
|
175
|
+
|
|
176
|
+
// The same interleaved-live-write axis on the delivery shape with NO second chance. A parked
|
|
177
|
+
// run raises its inbox card as a targeted `notification` event and never re-sends it, while the
|
|
178
|
+
// park ALSO fans out coarse `board` events whose debounced refresh is routinely in flight at
|
|
179
|
+
// that moment. A snapshot READ before the card existed used to replace the whole inbox and drop
|
|
180
|
+
// it, so the bell never appeared and nothing could bring it back — the `pr-review` e2e spec's
|
|
181
|
+
// 30s timeout on `notifications-bell`. The notifications store now stamps each live write and
|
|
182
|
+
// `refresh()` captures its baseline beside the board's.
|
|
183
|
+
it('a refresh started before a live notification does not drop the raised card', async () => {
|
|
184
|
+
const frame = block('f1')
|
|
185
|
+
let resolveRefresh!: (s: WorkspaceSnapshot) => void
|
|
186
|
+
const getWorkspace = vi
|
|
187
|
+
.fn()
|
|
188
|
+
// 1) switchTo — nothing in the inbox yet.
|
|
189
|
+
.mockResolvedValueOnce(snapshot('ws1', [frame]))
|
|
190
|
+
// 2) a refresh whose fetch is in flight while the run parks and raises its card.
|
|
191
|
+
.mockReturnValueOnce(new Promise<WorkspaceSnapshot>((r) => (resolveRefresh = r)))
|
|
192
|
+
vi.stubGlobal('useApi', () => ({ getWorkspace }))
|
|
193
|
+
|
|
194
|
+
const ws = useWorkspaceStore()
|
|
195
|
+
const notifications = useNotificationsStore()
|
|
196
|
+
await ws.switchTo('ws1')
|
|
197
|
+
|
|
198
|
+
// A refresh starts (captures the notifications baseline; its snapshot has an empty inbox).
|
|
199
|
+
const pass = ws.refresh()
|
|
200
|
+
// The run parks mid-fetch and pushes its card.
|
|
201
|
+
notifications.upsert(notification('n1'))
|
|
202
|
+
expect(notifications.count).toBe(1)
|
|
203
|
+
// The now-stale refresh resolves, still carrying the empty inbox it read.
|
|
204
|
+
resolveRefresh(snapshot('ws1', [frame]))
|
|
205
|
+
await pass
|
|
206
|
+
|
|
207
|
+
// The card survives: it is the only delivery there will ever be.
|
|
208
|
+
expect(notifications.count).toBe(1)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
// The mirror image, and the reason the guard tracks REMOVALS too: a card resolved live (acted
|
|
212
|
+
// on in another tab, or cleared by the engine) must not be resurrected by a snapshot that was
|
|
213
|
+
// read while it was still open — a resurrected card offers an action the server has already
|
|
214
|
+
// taken.
|
|
215
|
+
it('a refresh started before a live resolve does not resurrect the card', async () => {
|
|
216
|
+
const frame = block('f1')
|
|
217
|
+
let resolveRefresh!: (s: WorkspaceSnapshot) => void
|
|
218
|
+
const getWorkspace = vi
|
|
219
|
+
.fn()
|
|
220
|
+
// 1) switchTo — the card is open.
|
|
221
|
+
.mockResolvedValueOnce(snapshot('ws1', [frame], [notification('n1')]))
|
|
222
|
+
// 2) a refresh whose fetch is in flight while the card is resolved elsewhere.
|
|
223
|
+
.mockReturnValueOnce(new Promise<WorkspaceSnapshot>((r) => (resolveRefresh = r)))
|
|
224
|
+
vi.stubGlobal('useApi', () => ({ getWorkspace }))
|
|
225
|
+
|
|
226
|
+
const ws = useWorkspaceStore()
|
|
227
|
+
const notifications = useNotificationsStore()
|
|
228
|
+
await ws.switchTo('ws1')
|
|
229
|
+
expect(notifications.count).toBe(1)
|
|
230
|
+
|
|
231
|
+
const pass = ws.refresh()
|
|
232
|
+
notifications.upsert(notification('n1', { status: 'acted', resolvedAt: 2 }))
|
|
233
|
+
expect(notifications.count).toBe(0)
|
|
234
|
+
resolveRefresh(snapshot('ws1', [frame], [notification('n1')]))
|
|
235
|
+
await pass
|
|
236
|
+
|
|
237
|
+
expect(notifications.count).toBe(0)
|
|
238
|
+
})
|
|
152
239
|
})
|
|
153
240
|
|
|
154
241
|
// Cold-open waterfall flattening (app-startup initiative, item 8): `init()` fetches the persisted
|
package/app/stores/workspace.ts
CHANGED
|
@@ -9,6 +9,8 @@ import type {
|
|
|
9
9
|
} from '~/types/domain'
|
|
10
10
|
import { useAccountsStore } from '~/stores/accounts'
|
|
11
11
|
import { useBoardStore } from '~/stores/board'
|
|
12
|
+
import { useNotificationsStore } from '~/stores/notifications'
|
|
13
|
+
import type { LiveWriteBaselines } from '~/stores/workspace/hydrate'
|
|
12
14
|
import { applySnapshotToStores, resetPerBoardCaches } from '~/stores/workspace/hydrate'
|
|
13
15
|
import { createWorkspaceCommands } from '~/stores/workspace/commands'
|
|
14
16
|
import { createInfraSetupState } from '~/stores/workspace/infraSetup'
|
|
@@ -81,12 +83,12 @@ export const useWorkspaceStore = defineStore(
|
|
|
81
83
|
)
|
|
82
84
|
|
|
83
85
|
/**
|
|
84
|
-
* Push a snapshot into the data stores. `
|
|
85
|
-
* lets the
|
|
86
|
-
* slower refresh can't clobber
|
|
87
|
-
* fresh loads (init/switch/create), where there is no in-flight
|
|
86
|
+
* Push a snapshot into the data stores. `baselines` (captured BEFORE this snapshot's fetch)
|
|
87
|
+
* lets the replace-style stores preserve anything written live while the fetch was in flight,
|
|
88
|
+
* so a slower refresh can't clobber newer live state (see {@link LiveWriteBaselines}). Omitted
|
|
89
|
+
* by fresh loads (init/switch/create), where there is no in-flight race to guard.
|
|
88
90
|
*/
|
|
89
|
-
function hydrate(snapshot: WorkspaceSnapshot,
|
|
91
|
+
function hydrate(snapshot: WorkspaceSnapshot, baselines?: LiveWriteBaselines) {
|
|
90
92
|
// A change of active board (or the first load) drops the per-block caches that are NOT
|
|
91
93
|
// part of the snapshot; a same-board refresh keeps them (see `resetPerBoardCaches`).
|
|
92
94
|
if (workspaceId.value !== snapshot.workspace.id) resetPerBoardCaches()
|
|
@@ -107,7 +109,7 @@ export const useWorkspaceStore = defineStore(
|
|
|
107
109
|
workspaces.value.unshift(snapshot.workspace)
|
|
108
110
|
}
|
|
109
111
|
// Fan the rest of the snapshot out into the per-feature data stores.
|
|
110
|
-
applySnapshotToStores(snapshot,
|
|
112
|
+
applySnapshotToStores(snapshot, baselines)
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
/** Resolve accounts + boards, then open the right board for the active account. */
|
|
@@ -209,17 +211,20 @@ export const useWorkspaceStore = defineStore(
|
|
|
209
211
|
const targetId = workspaceId.value
|
|
210
212
|
if (!targetId) return
|
|
211
213
|
const seq = ++refreshSeq
|
|
212
|
-
// Capture the
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
const
|
|
214
|
+
// Capture the live-write baselines BEFORE the fetch: anything a live event writes while
|
|
215
|
+
// this (potentially slow) snapshot is in flight is newer than the snapshot, so `hydrate`
|
|
216
|
+
// must NOT clobber it back. The `refreshSeq` guard below only orders refreshes against each
|
|
217
|
+
// OTHER — this guards a refresh against an interleaved live write (a run's terminal status
|
|
218
|
+
// landing mid-fetch, or the inbox card it raises), the coherence hazard under CI latency.
|
|
219
|
+
const baselines: LiveWriteBaselines = {
|
|
220
|
+
board: useBoardStore().hydrateBaseline(),
|
|
221
|
+
notifications: useNotificationsStore().hydrateBaseline(),
|
|
222
|
+
}
|
|
218
223
|
const snapshot = await api.getWorkspace(targetId)
|
|
219
224
|
// A newer refresh was issued (or the active board switched) while this fetch was in flight —
|
|
220
225
|
// discard this older/staler result so it can't clobber the newer hydrate.
|
|
221
226
|
if (seq !== refreshSeq || workspaceId.value !== targetId) return
|
|
222
|
-
hydrate(snapshot,
|
|
227
|
+
hydrate(snapshot, baselines)
|
|
223
228
|
}
|
|
224
229
|
|
|
225
230
|
/** The active workspace id, or throw if the app isn't bootstrapped yet. */
|
package/i18n/locales/de.json
CHANGED
|
@@ -2000,11 +2000,11 @@
|
|
|
2000
2000
|
"unknown": "war nicht verfügbar ({reason})."
|
|
2001
2001
|
},
|
|
2002
2002
|
"remedy": {
|
|
2003
|
-
"harnessUnsupported": "Führen Sie den Schritt auf einer Agenten-CLI mit MCP aus, oder erweitern Sie die Harness-Liste des Servers.",
|
|
2003
|
+
"harnessUnsupported": "Führen Sie den Schritt auf einer Agenten-CLI mit MCP aus, oder erweitern Sie die Harness-Liste des Servers. Ein Codex-Lauf mit Ihrer eigenen CLI-Anmeldung hat keinen laufspezifischen Ablageort für den Server; verwenden Sie stattdessen Zugangsdaten der Plattform.",
|
|
2004
2004
|
"transportUnsupported": "Deklarieren Sie dafür einen stdio-Server, oder führen Sie den Schritt auf einer Agenten-CLI aus, die HTTP-Server erreicht.",
|
|
2005
|
-
"missingSecret": "Hinterlegen Sie die genannte Zugangsinformation im Infrastruktur-Fenster unter den Capability-Zugangsdaten.",
|
|
2005
|
+
"missingSecret": "Hinterlegen Sie die genannte Zugangsinformation entweder als Umgebungsvariable des Deployments oder im Infrastruktur-Fenster unter den Capability-Zugangsdaten.",
|
|
2006
2006
|
"reservedSecret": "Ändern Sie die Deklaration auf einen anderen Schlüssel; das Setzen dieser Variablen hilft gerade nicht.",
|
|
2007
|
-
"oauthNotConnected": "Verbinden Sie dieses Board im Infrastruktur-Fenster damit.",
|
|
2007
|
+
"oauthNotConnected": "Verbinden Sie dieses Board im Infrastruktur-Fenster damit. Ein Deployment ohne ENCRYPTION_KEY hat keinen Ort für eine Berechtigung, das muss ein Betreiber also zuerst setzen.",
|
|
2008
2008
|
"oauthTokenFailed": "Verbinden Sie es im Infrastruktur-Fenster neu, oder warten Sie die Störung des Anbieters ab.",
|
|
2009
2009
|
"overBudget": "Kürzen Sie, was der Agent deklariert, damit ein Lauf alles mitführen kann."
|
|
2010
2010
|
}
|
package/i18n/locales/en.json
CHANGED
|
@@ -1527,11 +1527,11 @@
|
|
|
1527
1527
|
"unknown": "was not available ({reason})."
|
|
1528
1528
|
},
|
|
1529
1529
|
"remedy": {
|
|
1530
|
-
"harnessUnsupported": "Run the step on an agent CLI that speaks MCP, or widen the server's harness list.",
|
|
1530
|
+
"harnessUnsupported": "Run the step on an agent CLI that speaks MCP, or widen the server's harness list. A Codex run signed in with your own CLI login has nowhere per-run to keep the server, so use a platform credential instead.",
|
|
1531
1531
|
"transportUnsupported": "Declare a stdio server for it, or run the step on an agent CLI that reaches HTTP servers.",
|
|
1532
|
-
"missingSecret": "Set the credential it names under capability credentials
|
|
1532
|
+
"missingSecret": "Set the credential it names, either as a deployment environment variable or under capability credentials in the Infrastructure window.",
|
|
1533
1533
|
"reservedSecret": "Change the declaration to ask for another key; setting that variable is exactly what will not help.",
|
|
1534
|
-
"oauthNotConnected": "Connect this board to it from the Infrastructure window.",
|
|
1534
|
+
"oauthNotConnected": "Connect this board to it from the Infrastructure window. A deployment with no ENCRYPTION_KEY has nowhere to keep a grant, so an operator has to set that first.",
|
|
1535
1535
|
"oauthTokenFailed": "Reconnect it from the Infrastructure window, or wait out the vendor's outage.",
|
|
1536
1536
|
"overBudget": "Trim what the agent declares, so one run can carry all of it."
|
|
1537
1537
|
}
|
package/i18n/locales/es.json
CHANGED
|
@@ -1436,11 +1436,11 @@
|
|
|
1436
1436
|
"unknown": "no estuvo disponible ({reason})."
|
|
1437
1437
|
},
|
|
1438
1438
|
"remedy": {
|
|
1439
|
-
"harnessUnsupported": "Ejecuta el paso en una CLI de agente que hable MCP, o amplía la lista de harnesses del servidor.",
|
|
1439
|
+
"harnessUnsupported": "Ejecuta el paso en una CLI de agente que hable MCP, o amplía la lista de harnesses del servidor. Una ejecución de Codex con tu propia sesión de la CLI no tiene dónde guardar el servidor durante esa ejecución, así que usa una credencial de la plataforma.",
|
|
1440
1440
|
"transportUnsupported": "Declara un servidor stdio para él, o ejecuta el paso en una CLI de agente que alcance servidores HTTP.",
|
|
1441
|
-
"missingSecret": "Configura la credencial que nombra en las credenciales de capacidades, en la ventana de Infraestructura.",
|
|
1441
|
+
"missingSecret": "Configura la credencial que nombra, ya sea como variable de entorno del despliegue o en las credenciales de capacidades, en la ventana de Infraestructura.",
|
|
1442
1442
|
"reservedSecret": "Cambia la declaración para que pida otra clave; definir esa variable es justo lo que no ayudará.",
|
|
1443
|
-
"oauthNotConnected": "Conecta este tablero con él desde la ventana de Infraestructura.",
|
|
1443
|
+
"oauthNotConnected": "Conecta este tablero con él desde la ventana de Infraestructura. Un despliegue sin ENCRYPTION_KEY no tiene dónde guardar una concesión, así que un operador debe configurarla primero.",
|
|
1444
1444
|
"oauthTokenFailed": "Vuelve a conectarlo desde la ventana de Infraestructura, o espera a que pase la caída del proveedor.",
|
|
1445
1445
|
"overBudget": "Recorta lo que declara el agente, para que una ejecución pueda llevarlo todo."
|
|
1446
1446
|
}
|
package/i18n/locales/fr.json
CHANGED
|
@@ -1436,11 +1436,11 @@
|
|
|
1436
1436
|
"unknown": "n'était pas disponible ({reason})."
|
|
1437
1437
|
},
|
|
1438
1438
|
"remedy": {
|
|
1439
|
-
"harnessUnsupported": "Exécutez l’étape sur une CLI d’agent qui parle MCP, ou élargissez la liste de harnesses du serveur.",
|
|
1439
|
+
"harnessUnsupported": "Exécutez l’étape sur une CLI d’agent qui parle MCP, ou élargissez la liste de harnesses du serveur. Une exécution Codex connectée avec votre propre session CLI n’a nulle part où conserver le serveur le temps de l’exécution : utilisez plutôt un identifiant de la plateforme.",
|
|
1440
1440
|
"transportUnsupported": "Déclarez un serveur stdio à sa place, ou exécutez l’étape sur une CLI d’agent qui atteint les serveurs HTTP.",
|
|
1441
|
-
"missingSecret": "Renseignez l’identifiant qu’il nomme dans les identifiants de capacités, depuis la fenêtre Infrastructure.",
|
|
1441
|
+
"missingSecret": "Renseignez l’identifiant qu’il nomme, soit comme variable d’environnement du déploiement, soit dans les identifiants de capacités, depuis la fenêtre Infrastructure.",
|
|
1442
1442
|
"reservedSecret": "Modifiez la déclaration pour demander une autre clé ; définir cette variable est précisément ce qui n’aidera pas.",
|
|
1443
|
-
"oauthNotConnected": "Connectez ce tableau à ce serveur depuis la fenêtre Infrastructure.",
|
|
1443
|
+
"oauthNotConnected": "Connectez ce tableau à ce serveur depuis la fenêtre Infrastructure. Un déploiement sans ENCRYPTION_KEY n’a nulle part où conserver une autorisation : un opérateur doit d’abord la définir.",
|
|
1444
1444
|
"oauthTokenFailed": "Reconnectez-le depuis la fenêtre Infrastructure, ou attendez la fin de la panne du fournisseur.",
|
|
1445
1445
|
"overBudget": "Réduisez ce que l’agent déclare, pour qu’une exécution puisse tout transporter."
|
|
1446
1446
|
}
|
package/i18n/locales/he.json
CHANGED
|
@@ -1436,11 +1436,11 @@
|
|
|
1436
1436
|
"unknown": "לא היה זמין ({reason})."
|
|
1437
1437
|
},
|
|
1438
1438
|
"remedy": {
|
|
1439
|
-
"harnessUnsupported": "הריצו את השלב על ממשק סוכן שדובר MCP, או הרחיבו את רשימת המעטפות של השרת.",
|
|
1439
|
+
"harnessUnsupported": "הריצו את השלב על ממשק סוכן שדובר MCP, או הרחיבו את רשימת המעטפות של השרת. בהרצת Codex עם חיבור ה-CLI שלכם אין מקום ייעודי להרצה לשמירת השרת, ולכן השתמשו באישור גישה של הפלטפורמה במקום.",
|
|
1440
1440
|
"transportUnsupported": "הצהירו עבורו שרת stdio, או הריצו את השלב על ממשק סוכן שמגיע לשרתי HTTP.",
|
|
1441
|
-
"missingSecret": "הגדירו את אישור הגישה שהוא
|
|
1441
|
+
"missingSecret": "הגדירו את אישור הגישה שהוא מבקש, בין כמשתנה סביבה של הפריסה ובין באישורי היכולות בחלון התשתית.",
|
|
1442
1442
|
"reservedSecret": "שנו את ההצהרה כך שתבקש מפתח אחר; הגדרת המשתנה הזה היא בדיוק מה שלא יעזור.",
|
|
1443
|
-
"oauthNotConnected": "חברו את הלוח הזה אליו מחלון התשתית.",
|
|
1443
|
+
"oauthNotConnected": "חברו את הלוח הזה אליו מחלון התשתית. בפריסה ללא ENCRYPTION_KEY אין היכן לשמור הרשאה, ולכן מפעיל צריך להגדיר אותו קודם.",
|
|
1444
1444
|
"oauthTokenFailed": "חברו אותו מחדש מחלון התשתית, או המתינו לסיום התקלה אצל הספק.",
|
|
1445
1445
|
"overBudget": "צמצמו את מה שהסוכן מצהיר עליו, כדי שריצה אחת תוכל לשאת הכול."
|
|
1446
1446
|
}
|
package/i18n/locales/it.json
CHANGED
|
@@ -2000,11 +2000,11 @@
|
|
|
2000
2000
|
"unknown": "non era disponibile ({reason})."
|
|
2001
2001
|
},
|
|
2002
2002
|
"remedy": {
|
|
2003
|
-
"harnessUnsupported": "Esegui il passo su una CLI dell'agente che parla MCP, oppure amplia l'elenco di harness del server.",
|
|
2003
|
+
"harnessUnsupported": "Esegui il passo su una CLI dell'agente che parla MCP, oppure amplia l'elenco di harness del server. Un'esecuzione Codex con il tuo login della CLI non ha dove tenere il server per quell'esecuzione, quindi usa una credenziale della piattaforma.",
|
|
2004
2004
|
"transportUnsupported": "Dichiara un server stdio al suo posto, oppure esegui il passo su una CLI dell'agente che raggiunge i server HTTP.",
|
|
2005
|
-
"missingSecret": "Imposta la credenziale che indica nelle credenziali delle capability, dalla finestra Infrastruttura.",
|
|
2005
|
+
"missingSecret": "Imposta la credenziale che indica, come variabile d'ambiente del deployment oppure nelle credenziali delle capability, dalla finestra Infrastruttura.",
|
|
2006
2006
|
"reservedSecret": "Cambia la dichiarazione perché chieda un'altra chiave; impostare quella variabile è proprio ciò che non aiuterà.",
|
|
2007
|
-
"oauthNotConnected": "Collega questa lavagna al server dalla finestra Infrastruttura.",
|
|
2007
|
+
"oauthNotConnected": "Collega questa lavagna al server dalla finestra Infrastruttura. Un deployment senza ENCRYPTION_KEY non ha dove conservare una concessione, quindi un operatore deve impostarla prima.",
|
|
2008
2008
|
"oauthTokenFailed": "Ricollegalo dalla finestra Infrastruttura, oppure attendi la fine del disservizio del fornitore.",
|
|
2009
2009
|
"overBudget": "Riduci ciò che l'agente dichiara, così una singola esecuzione può portarlo tutto."
|
|
2010
2010
|
}
|
package/i18n/locales/ja.json
CHANGED
|
@@ -1436,11 +1436,11 @@
|
|
|
1436
1436
|
"unknown": "は利用できませんでした({reason})。"
|
|
1437
1437
|
},
|
|
1438
1438
|
"remedy": {
|
|
1439
|
-
"harnessUnsupported": "MCP を話すエージェント CLI
|
|
1439
|
+
"harnessUnsupported": "MCP を話すエージェント CLI でステップを実行するか、サーバーのハーネス一覧を広げてください。自分の CLI ログインでサインインした Codex の実行には、サーバーを実行ごとに保管する場所がないため、代わりにプラットフォームの認証情報を使ってください。",
|
|
1440
1440
|
"transportUnsupported": "このサーバー用に stdio サーバーを宣言するか、HTTP サーバーに接続できるエージェント CLI でステップを実行してください。",
|
|
1441
|
-
"missingSecret": "
|
|
1441
|
+
"missingSecret": "要求されている認証情報を、デプロイの環境変数として設定するか、インフラストラクチャ ウィンドウのケイパビリティ認証情報で設定してください。",
|
|
1442
1442
|
"reservedSecret": "別のキーを要求するよう宣言を変更してください。その変数を設定しても解決しません。",
|
|
1443
|
-
"oauthNotConnected": "インフラストラクチャ ウィンドウからこのボードを接続してください。",
|
|
1443
|
+
"oauthNotConnected": "インフラストラクチャ ウィンドウからこのボードを接続してください。ENCRYPTION_KEY のないデプロイには許可を保管する場所がないため、まず運用者がそれを設定する必要があります。",
|
|
1444
1444
|
"oauthTokenFailed": "インフラストラクチャ ウィンドウから接続し直すか、提供元の障害が収まるのを待ってください。",
|
|
1445
1445
|
"overBudget": "1 回の実行ですべて運べるよう、このエージェントの宣言を減らしてください。"
|
|
1446
1446
|
}
|
package/i18n/locales/pl.json
CHANGED
|
@@ -1436,11 +1436,11 @@
|
|
|
1436
1436
|
"unknown": "nie był dostępny ({reason})."
|
|
1437
1437
|
},
|
|
1438
1438
|
"remedy": {
|
|
1439
|
-
"harnessUnsupported": "Uruchom krok na CLI agenta, które zna MCP, albo poszerz listę harnessów serwera.",
|
|
1439
|
+
"harnessUnsupported": "Uruchom krok na CLI agenta, które zna MCP, albo poszerz listę harnessów serwera. Uruchomienie Codex z Twoim własnym logowaniem do CLI nie ma gdzie przechować serwera na czas przebiegu, więc użyj poświadczenia platformy.",
|
|
1440
1440
|
"transportUnsupported": "Zadeklaruj dla niego serwer stdio albo uruchom krok na CLI agenta, które dosięga serwerów HTTP.",
|
|
1441
|
-
"missingSecret": "Ustaw wskazane poświadczenie w poświadczeniach możliwości, w oknie Infrastruktura.",
|
|
1441
|
+
"missingSecret": "Ustaw wskazane poświadczenie jako zmienną środowiskową wdrożenia albo w poświadczeniach możliwości, w oknie Infrastruktura.",
|
|
1442
1442
|
"reservedSecret": "Zmień deklarację tak, by prosiła o inny klucz; ustawienie tej zmiennej to właśnie to, co nie pomoże.",
|
|
1443
|
-
"oauthNotConnected": "Połącz tę tablicę z serwerem w oknie Infrastruktura.",
|
|
1443
|
+
"oauthNotConnected": "Połącz tę tablicę z serwerem w oknie Infrastruktura. Wdrożenie bez ENCRYPTION_KEY nie ma gdzie przechować zgody, więc operator musi ją najpierw ustawić.",
|
|
1444
1444
|
"oauthTokenFailed": "Połącz go ponownie w oknie Infrastruktura albo przeczekaj awarię dostawcy.",
|
|
1445
1445
|
"overBudget": "Skróć to, co deklaruje agent, aby jedno uruchomienie uniosło całość."
|
|
1446
1446
|
}
|
package/i18n/locales/tr.json
CHANGED
|
@@ -1436,11 +1436,11 @@
|
|
|
1436
1436
|
"unknown": "kullanılamadı ({reason})."
|
|
1437
1437
|
},
|
|
1438
1438
|
"remedy": {
|
|
1439
|
-
"harnessUnsupported": "Adımı MCP konuşan bir ajan CLI'sinde çalıştırın ya da sunucunun harness listesini genişletin.",
|
|
1439
|
+
"harnessUnsupported": "Adımı MCP konuşan bir ajan CLI'sinde çalıştırın ya da sunucunun harness listesini genişletin. Kendi CLI oturumunuzla yapılan bir Codex çalıştırmasında sunucuyu o çalıştırmaya özel tutacak bir yer yoktur; bunun yerine bir platform kimlik bilgisi kullanın.",
|
|
1440
1440
|
"transportUnsupported": "Onun için bir stdio sunucusu bildirin ya da adımı HTTP sunucularına erişen bir ajan CLI'sinde çalıştırın.",
|
|
1441
|
-
"missingSecret": "Adı geçen kimlik bilgisini Altyapı penceresindeki yetenek kimlik bilgilerinde ayarlayın.",
|
|
1441
|
+
"missingSecret": "Adı geçen kimlik bilgisini dağıtımın ortam değişkeni olarak ya da Altyapı penceresindeki yetenek kimlik bilgilerinde ayarlayın.",
|
|
1442
1442
|
"reservedSecret": "Bildirimi başka bir anahtar isteyecek şekilde değiştirin; o değişkeni ayarlamak tam da yardımcı olmayacak şeydir.",
|
|
1443
|
-
"oauthNotConnected": "Bu panoyu Altyapı penceresinden ona bağlayın.",
|
|
1443
|
+
"oauthNotConnected": "Bu panoyu Altyapı penceresinden ona bağlayın. ENCRYPTION_KEY olmayan bir dağıtımda izni saklayacak bir yer yoktur, bu yüzden önce bir operatörün bunu ayarlaması gerekir.",
|
|
1444
1444
|
"oauthTokenFailed": "Altyapı penceresinden yeniden bağlayın ya da sağlayıcının kesintisinin geçmesini bekleyin.",
|
|
1445
1445
|
"overBudget": "Tek bir çalıştırma hepsini taşıyabilsin diye ajanın bildirdiklerini kısaltın."
|
|
1446
1446
|
}
|
package/i18n/locales/uk.json
CHANGED
|
@@ -1436,11 +1436,11 @@
|
|
|
1436
1436
|
"unknown": "був недоступний ({reason})."
|
|
1437
1437
|
},
|
|
1438
1438
|
"remedy": {
|
|
1439
|
-
"harnessUnsupported": "Виконайте крок на CLI агента, який володіє MCP, або розширте список harness сервера.",
|
|
1439
|
+
"harnessUnsupported": "Виконайте крок на CLI агента, який володіє MCP, або розширте список harness сервера. Запуск Codex із вашим власним входом у CLI не має де зберігати сервер у межах запуску, тож використайте облікові дані платформи.",
|
|
1440
1440
|
"transportUnsupported": "Оголосіть для нього сервер stdio або виконайте крок на CLI агента, що досягає серверів HTTP.",
|
|
1441
|
-
"missingSecret": "Задайте названі облікові дані в облікових даних
|
|
1441
|
+
"missingSecret": "Задайте названі облікові дані або як змінну середовища розгортання, або в облікових даних можливостей у вікні інфраструктури.",
|
|
1442
1442
|
"reservedSecret": "Змініть оголошення так, щоб воно просило інший ключ; задати цю змінну це саме те, що не допоможе.",
|
|
1443
|
-
"oauthNotConnected": "Під’єднайте цю дошку до нього у вікні інфраструктури.",
|
|
1443
|
+
"oauthNotConnected": "Під’єднайте цю дошку до нього у вікні інфраструктури. Розгортання без ENCRYPTION_KEY не має де зберігати дозвіл, тож оператор має спершу задати його.",
|
|
1444
1444
|
"oauthTokenFailed": "Під’єднайте його заново у вікні інфраструктури або перечекайте збій постачальника.",
|
|
1445
1445
|
"overBudget": "Скоротіть те, що оголошує агент, щоб один запуск ніс усе."
|
|
1446
1446
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.241.
|
|
3
|
+
"version": "0.241.2",
|
|
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.261.
|
|
43
|
+
"@cat-factory/contracts": "0.261.1"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@toad-contracts/testing": "0.3.2",
|