@iloveagents/foundry-agent 0.3.0 → 0.4.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/README.md +16 -0
- package/dist/client/agui-runner.d.ts +53 -0
- package/dist/client/agui-runner.js +320 -0
- package/dist/client/runner-events.d.ts +54 -0
- package/dist/client/runner-events.js +1 -0
- package/dist/client/service-fetch.d.ts +112 -0
- package/dist/client/service-fetch.js +244 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +10 -0
- package/dist/msal/auth-config.d.ts +91 -0
- package/dist/msal/auth-config.js +70 -0
- package/dist/msal/auth-store.d.ts +95 -0
- package/dist/msal/auth-store.js +372 -0
- package/dist/msal/index.d.ts +3 -0
- package/dist/msal/index.js +3 -0
- package/dist/msal/token-fetch.d.ts +16 -0
- package/dist/msal/token-fetch.js +57 -0
- package/dist/store/citation-store.d.ts +42 -0
- package/dist/store/citation-store.js +14 -0
- package/dist/store/link-store.d.ts +29 -0
- package/dist/store/link-store.js +28 -0
- package/dist/store/streaming-status-store.d.ts +15 -0
- package/dist/store/streaming-status-store.js +9 -0
- package/dist/tools/registry.d.ts +48 -0
- package/dist/tools/registry.js +50 -0
- package/package.json +23 -9
- package/AGENTS.md +0 -91
- package/CHANGELOG.md +0 -180
- package/CLAUDE.md +0 -1
- package/src/__tests__/agui-runner.test.ts +0 -404
- package/src/__tests__/auth-store.test.ts +0 -596
- package/src/__tests__/citation-store.test.ts +0 -52
- package/src/__tests__/client-tool-registry.test.ts +0 -84
- package/src/__tests__/link-store.test.ts +0 -48
- package/src/__tests__/service-fetch.test.ts +0 -525
- package/src/__tests__/streaming-status-store.test.ts +0 -22
- package/src/__tests__/token-fetch.test.ts +0 -134
- package/src/client/agui-runner.ts +0 -382
- package/src/client/runner-events.ts +0 -27
- package/src/client/service-fetch.ts +0 -318
- package/src/index.ts +0 -27
- package/src/msal/auth-config.ts +0 -150
- package/src/msal/auth-store.ts +0 -517
- package/src/msal/index.ts +0 -14
- package/src/msal/token-fetch.ts +0 -68
- package/src/store/citation-store.ts +0 -52
- package/src/store/link-store.ts +0 -53
- package/src/store/streaming-status-store.ts +0 -21
- package/src/tools/registry.ts +0 -112
- package/tsconfig.json +0 -15
- package/vitest.config.ts +0 -8
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createStore } from "zustand/vanilla";
|
|
2
|
+
export const clientToolRegistry = createStore((set, get) => ({
|
|
3
|
+
globalTools: new Map(),
|
|
4
|
+
pageTools: new Map(),
|
|
5
|
+
registerGlobal: (tool) => {
|
|
6
|
+
const { globalTools } = get();
|
|
7
|
+
const next = new Map(globalTools);
|
|
8
|
+
next.set(tool.name, tool);
|
|
9
|
+
set({ globalTools: next });
|
|
10
|
+
},
|
|
11
|
+
registerPageTools: (tools) => {
|
|
12
|
+
const next = new Map();
|
|
13
|
+
for (const tool of tools) {
|
|
14
|
+
next.set(tool.name, tool);
|
|
15
|
+
}
|
|
16
|
+
set({ pageTools: next });
|
|
17
|
+
},
|
|
18
|
+
clearPageTools: () => {
|
|
19
|
+
set({ pageTools: new Map() });
|
|
20
|
+
},
|
|
21
|
+
getActiveSchemas: () => {
|
|
22
|
+
const { globalTools, pageTools } = get();
|
|
23
|
+
// Merge: page tools override global tools with the same name
|
|
24
|
+
const merged = new Map();
|
|
25
|
+
for (const tool of globalTools.values()) {
|
|
26
|
+
merged.set(tool.name, tool);
|
|
27
|
+
}
|
|
28
|
+
for (const tool of pageTools.values()) {
|
|
29
|
+
merged.set(tool.name, tool); // page overrides global
|
|
30
|
+
}
|
|
31
|
+
return Array.from(merged.values()).map((tool) => ({
|
|
32
|
+
name: tool.name,
|
|
33
|
+
description: tool.description,
|
|
34
|
+
parameters: tool.parameters,
|
|
35
|
+
}));
|
|
36
|
+
},
|
|
37
|
+
isRegistered: (name) => {
|
|
38
|
+
const { globalTools, pageTools } = get();
|
|
39
|
+
return pageTools.has(name) || globalTools.has(name);
|
|
40
|
+
},
|
|
41
|
+
executeTool: async (name, argsJson) => {
|
|
42
|
+
const { globalTools, pageTools } = get();
|
|
43
|
+
// Page tools take precedence
|
|
44
|
+
const tool = pageTools.get(name) ?? globalTools.get(name);
|
|
45
|
+
if (!tool) {
|
|
46
|
+
return JSON.stringify({ error: `Unknown client tool: ${name}` });
|
|
47
|
+
}
|
|
48
|
+
return tool.execute(argsJson);
|
|
49
|
+
},
|
|
50
|
+
}));
|
package/package.json
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iloveagents/foundry-agent",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"license": "
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
7
8
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./msal": {
|
|
14
|
+
"types": "./dist/msal/index.d.ts",
|
|
15
|
+
"import": "./dist/msal/index.js"
|
|
16
|
+
}
|
|
10
17
|
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
11
23
|
"publishConfig": {
|
|
12
24
|
"access": "public"
|
|
13
25
|
},
|
|
@@ -23,14 +35,16 @@
|
|
|
23
35
|
}
|
|
24
36
|
},
|
|
25
37
|
"devDependencies": {
|
|
38
|
+
"@ag-ui/client": "^0.0.52",
|
|
39
|
+
"@ag-ui/core": "^0.0.52",
|
|
40
|
+
"@azure/msal-browser": "^5.0.0",
|
|
41
|
+
"jsdom": "^28.1.0",
|
|
26
42
|
"typescript": "~5.9.3",
|
|
27
43
|
"vitest": "^4.1.4",
|
|
28
|
-
"
|
|
29
|
-
"zustand": "^5.0.0",
|
|
30
|
-
"@ag-ui/client": "^0.0.52",
|
|
31
|
-
"@ag-ui/core": "^0.0.52"
|
|
44
|
+
"zustand": "^5.0.0"
|
|
32
45
|
},
|
|
33
46
|
"scripts": {
|
|
47
|
+
"build": "tsc -p tsconfig.build.json && node ../../scripts/fix-dts-extensions.mjs dist",
|
|
34
48
|
"test:unit": "vitest run",
|
|
35
49
|
"typecheck": "tsc --noEmit"
|
|
36
50
|
}
|
package/AGENTS.md
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
Pure-TS agent transport for the LastSpace open-core stack. Published as `@iloveagents/foundry-agent`. Zero DOM, zero React, zero `@assistant-ui/*`. Cross-runtime — consumed by `@iloveagents/foundry-web-ui` today, future Outlook / Teams / native shells tomorrow.
|
|
2
|
-
|
|
3
|
-
# Architecture
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
src/
|
|
7
|
-
client/
|
|
8
|
-
agui-runner.ts ← AG-UI protocol engine (SSE + multi-turn loop). Yields RunnerEvent.
|
|
9
|
-
runner-events.ts ← RunnerEvent discriminated union.
|
|
10
|
-
service-fetch.ts ← createServiceFetch({ acquireToken, baseUrl }) factory.
|
|
11
|
-
store/
|
|
12
|
-
streaming-status-store.ts ← vanilla zustand store
|
|
13
|
-
citation-store.ts ← vanilla zustand store
|
|
14
|
-
tools/
|
|
15
|
-
registry.ts ← clientToolRegistry (vanilla zustand store).
|
|
16
|
-
msal/ ← subpath export: @iloveagents/foundry-agent/msal
|
|
17
|
-
auth-store.ts ← authStore (vanilla zustand store)
|
|
18
|
-
auth-config.ts ← MsalAuthConfig + initializeMsal singleton.
|
|
19
|
-
token-fetch.ts ← Bearer-token-attaching fetch.
|
|
20
|
-
index.ts ← barrel for the /msal subpath
|
|
21
|
-
index.ts ← barrel for the root export.
|
|
22
|
-
__tests__/ ← vitest suite (runner, service-fetch, no-react smoke).
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
# Import boundary (enforced by guard test)
|
|
26
|
-
|
|
27
|
-
Files under `packages/agent/src/` (excluding `msal/`) MUST NOT import:
|
|
28
|
-
|
|
29
|
-
- `react`, `react-dom`
|
|
30
|
-
- `@assistant-ui/*` (anywhere — including under `msal/`)
|
|
31
|
-
- `@azure/msal-*` (anywhere — including under `msal/` for `msal-react`; only `@azure/msal-browser` is allowed under `msal/`)
|
|
32
|
-
- bare `"zustand"` — use `"zustand/vanilla"` (`zustand/middleware` is allowed)
|
|
33
|
-
|
|
34
|
-
Files under `packages/agent/src/msal/` MAY import `@azure/msal-browser` (dynamic import only — keeps the dep optional). They MUST NOT import `@azure/msal-react` (which is React-bound and lives in `@iloveagents/foundry-web-ui`).
|
|
35
|
-
|
|
36
|
-
`packages/agent/` MUST NOT import from any other `@lastspace/*` package — agent is the leaf of the open-core graph.
|
|
37
|
-
|
|
38
|
-
# Why `zustand/vanilla`
|
|
39
|
-
|
|
40
|
-
`import { create } from "zustand"` resolves to the React build (`useSyncExternalStore`). Importing it in a "zero React" package would silently take a React dependency and defeat the cross-runtime goal. Every store in this package uses `import { createStore } from "zustand/vanilla"`. React consumers (in `@iloveagents/foundry-web-ui` / `apps/web/`) bind via `useStore(store, selector)` from the React entry of zustand.
|
|
41
|
-
|
|
42
|
-
# What goes here
|
|
43
|
-
|
|
44
|
-
- AG-UI protocol client (SSE, message conversion, multi-turn re-issue, tool dispatch).
|
|
45
|
-
- Vanilla state stores that the protocol engine needs at runtime — streaming status, citation cache, client-tool registry.
|
|
46
|
-
- MSAL bits under the `/msal` subpath only.
|
|
47
|
-
- Generic per-service fetch factory (`createServiceFetch`).
|
|
48
|
-
|
|
49
|
-
# What does NOT go here
|
|
50
|
-
|
|
51
|
-
- Anything implementing `@assistant-ui/*` interfaces (`ChatModelAdapter`, `AttachmentAdapter`, etc.) — those are React UI surface and live in `@iloveagents/foundry-web-ui`.
|
|
52
|
-
- React components, hooks, contexts, providers.
|
|
53
|
-
- UI-layout state (panel widths, modal modes, theme).
|
|
54
|
-
- The `MsalProvider` React component — that lives in `@iloveagents/foundry-web-ui`.
|
|
55
|
-
|
|
56
|
-
# Public surface
|
|
57
|
-
|
|
58
|
-
Root `@iloveagents/foundry-agent`:
|
|
59
|
-
|
|
60
|
-
```ts
|
|
61
|
-
import {
|
|
62
|
-
AGUIRunner,
|
|
63
|
-
type RunnerEvent,
|
|
64
|
-
createServiceFetch,
|
|
65
|
-
clientToolRegistry,
|
|
66
|
-
type ClientToolEntry,
|
|
67
|
-
streamingStatusStore,
|
|
68
|
-
type StreamingStatus,
|
|
69
|
-
citationStore,
|
|
70
|
-
type CitationResult,
|
|
71
|
-
type CitationHandler,
|
|
72
|
-
} from "@iloveagents/foundry-agent";
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Subpath `@iloveagents/foundry-agent/msal`:
|
|
76
|
-
|
|
77
|
-
```ts
|
|
78
|
-
import {
|
|
79
|
-
authStore,
|
|
80
|
-
type AuthUser,
|
|
81
|
-
type MsalAuthConfig,
|
|
82
|
-
initializeMsal,
|
|
83
|
-
getMsalInstance,
|
|
84
|
-
getMsalConfig,
|
|
85
|
-
tokenFetch,
|
|
86
|
-
} from "@iloveagents/foundry-agent/msal";
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
# Bundle-size budget
|
|
90
|
-
|
|
91
|
-
Target ≤25 KB gz on the root entry (`@iloveagents/foundry-agent`, excluding the `/msal` subpath). Measured manually pre-1.0 via `gzip -c dist/index.js | wc -c`; `size-limit` enforcement scaffolds in #92's follow-up.
|
package/CHANGELOG.md
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
# @iloveagents/foundry-agent
|
|
2
|
-
|
|
3
|
-
## 0.3.0
|
|
4
|
-
|
|
5
|
-
## 0.2.2
|
|
6
|
-
|
|
7
|
-
### Patch Changes
|
|
8
|
-
|
|
9
|
-
- 8184a8c: shell: URL is authoritative for the runtime threadId — fixes
|
|
10
|
-
resume-creates-new-conversation race
|
|
11
|
-
|
|
12
|
-
`ChatConversationAwareRuntime` previously computed
|
|
13
|
-
`effectiveThreadId = sticky ?? urlMatch ?? freshIdRef.current`.
|
|
14
|
-
When the user navigated from one chat to another via a Recents
|
|
15
|
-
click, the synchronous render that followed the URL change had:
|
|
16
|
-
- `urlMatch = B` (read from the now-updated `useLocation`)
|
|
17
|
-
- `sticky = A` (the active-chat-store hadn't been updated by
|
|
18
|
-
`useTrackActiveChatFromUrl`'s `useEffect` yet — effects run
|
|
19
|
-
AFTER the commit phase)
|
|
20
|
-
|
|
21
|
-
`sticky ?? urlMatch` picked `A` (the previous chat). The AG-UI
|
|
22
|
-
adapter was constructed with `threadId = A`. The first message the
|
|
23
|
-
user sent went to `/api/agent` with `thread_id = A`, the middleware
|
|
24
|
-
created a new row keyed by `A`... wait actually no, here's what
|
|
25
|
-
happens — the runtime mints a fresh runner UUID when given a
|
|
26
|
-
mismatched threadId; that fresh UUID became a new conversation row,
|
|
27
|
-
appearing as "Untitled chat" at the top of the sidebar while the
|
|
28
|
-
URL still said `/chat/B`. Two active-looking dots: one for `B`
|
|
29
|
-
(NavLink URL match), one for the new row (statusDot from the
|
|
30
|
-
updated active-chat-store after the lazy ensure ran).
|
|
31
|
-
|
|
32
|
-
Swap the priority: `urlMatch ?? sticky ?? freshIdRef.current`. The
|
|
33
|
-
URL is authoritative whenever it's set (i.e. on `/chat/<id>`
|
|
34
|
-
routes), eliminating the stale-sticky race entirely. `sticky` is
|
|
35
|
-
still consulted as the second-priority fallback for non-chat
|
|
36
|
-
routes (`/spaces`, `/tasks`) so the popout chat stays "live" while
|
|
37
|
-
the user browses workspaces — that's the original purpose of
|
|
38
|
-
`useStickyConversationId` and it's preserved.
|
|
39
|
-
|
|
40
|
-
## 0.2.1
|
|
41
|
-
|
|
42
|
-
### Patch Changes
|
|
43
|
-
|
|
44
|
-
- No source changes. The 0.2.0 publish for `@iloveagents/foundry-agent` and
|
|
45
|
-
`@iloveagents/foundry-web-primitives` failed because npm had `0.2.0`
|
|
46
|
-
reserved for those two packages from an older publish-then-unpublish
|
|
47
|
-
cycle (the version was reserved before the recent v1 incident — see
|
|
48
|
-
git tag history). `@iloveagents/foundry-web-shell@0.2.0` and
|
|
49
|
-
`@iloveagents/foundry-web-ui@0.2.0` published cleanly. Bumping the
|
|
50
|
-
whole fixed group to 0.2.1 so all four publish together at a version
|
|
51
|
-
number that's free across all four packages.
|
|
52
|
-
|
|
53
|
-
## 0.2.0
|
|
54
|
-
|
|
55
|
-
### Minor Changes
|
|
56
|
-
|
|
57
|
-
- shell: ChatConversationConfig API + sticky-runtime threadId
|
|
58
|
-
|
|
59
|
-
Lets a downstream module (e.g. `@ltwlf/spaces-web-ui`) attach a
|
|
60
|
-
`ChatConversationConfig` to its `ChatModule`. The shell then:
|
|
61
|
-
- Pre-mints a fresh thread UUID via `useRef` so the AG-UI runtime's
|
|
62
|
-
`threadId` is defined from first render — no `undefined → defined`
|
|
63
|
-
remount mid-conversation when the user sends the first message.
|
|
64
|
-
- Calls `config.useStickyConversationId()` (when declared) and
|
|
65
|
-
feeds the resulting id into the runtime so the chat stays "live"
|
|
66
|
-
while the user browses non-chat routes (Workspaces, Jobs, etc.).
|
|
67
|
-
- Per-config keyed child components (`StickyAwareRuntime` vs
|
|
68
|
-
`UrlOnlyRuntime`) so optional hooks satisfy Rules-of-Hooks across
|
|
69
|
-
multi-module config swaps.
|
|
70
|
-
|
|
71
|
-
UI: collapsible nav groups in `sidebar.tsx` with persisted state via
|
|
72
|
-
`useSyncExternalStore` + localStorage, plus stable group ordering via
|
|
73
|
-
new `priority` field in `nav-config.ts`.
|
|
74
|
-
|
|
75
|
-
`ag-ui-runtime-provider.tsx` accepts an external `threadId` and
|
|
76
|
-
`historyAdapterFactory` so the consuming app can wire its own
|
|
77
|
-
persistence (e.g. `ThreadHistoryAdapter` → REST `/api/conversations`).
|
|
78
|
-
|
|
79
|
-
### Patch Changes
|
|
80
|
-
|
|
81
|
-
- Fix `citationStore.clear()` so the registered handler survives per-conversation resets.
|
|
82
|
-
|
|
83
|
-
The previous implementation wiped both `results` AND `handler`. `useNewConversation` calls `clear()` on every "New Thread" click. Combined with the `registerOnce`-style handler registration in feature modules (e.g. SPACES' `registerSpacesCitationHandler`), the result was that all `[n]` citation markers in chat became inert non-clickable spans for the rest of the session after the first new-thread click. `clear()` now resets `results` only — the handler is module-level state and must persist.
|
|
84
|
-
|
|
85
|
-
- Move sibling `@iloveagents/foundry-*` declarations from `peerDependencies`
|
|
86
|
-
to `dependencies` in `foundry-web-shell` and `foundry-web-ui`. These four
|
|
87
|
-
packages always ship together as a coordinated fixed group from the same
|
|
88
|
-
monorepo — they were never independently-versioned peers, so declaring
|
|
89
|
-
them as peerDeps caused changesets' `shouldBumpMajor` cascade to promote
|
|
90
|
-
the entire group to a major version on every minor changeset (a single
|
|
91
|
-
minor on any of the four would exit the sibling's `^0.x.y` peerDep range
|
|
92
|
-
→ all dependents promoted to major → fixed group synced all four to
|
|
93
|
-
major → 0.1.5 unexpectedly jumped to 1.0.0). Moving to `dependencies`
|
|
94
|
-
eliminates the cascade entirely; `dependencies` never trigger
|
|
95
|
-
`shouldBumpMajor`. Consumers that already install each foundry-\* package
|
|
96
|
-
directly are unaffected — pnpm hoists by name, so a single instance
|
|
97
|
-
resolves across the dep graph regardless of declaration site.
|
|
98
|
-
|
|
99
|
-
### NOTE: 1.0.0 / 1.0.1 were accidental
|
|
100
|
-
|
|
101
|
-
These version numbers were published briefly on 2026-05-27 due to the
|
|
102
|
-
peerDep cascade bug described above, then unpublished. The version
|
|
103
|
-
numbers themselves are now permanently reserved on npm and CANNOT be
|
|
104
|
-
re-published. This 0.2.0 release captures all the work that was in
|
|
105
|
-
the accidental 1.0.x releases. Do not depend on 1.0.x.
|
|
106
|
-
|
|
107
|
-
## 0.1.5
|
|
108
|
-
|
|
109
|
-
## 0.1.4
|
|
110
|
-
|
|
111
|
-
### Patch Changes
|
|
112
|
-
|
|
113
|
-
- 07ace7e: fix(agent): canonical MSAL.js recovery — acquireTokenRedirect-first, orphaned-state cleanup, dedup
|
|
114
|
-
|
|
115
|
-
Closes the production "stuck on 401 forever" bug observed on andritz-dev after the 0.1.2 / 0.1.3 fixes. Diagnostic on the live tab showed `getAllAccounts() === []` despite localStorage holding 3 access tokens + 1 refresh token — the orphaned-state signature.
|
|
116
|
-
|
|
117
|
-
**Root cause:** The previous recovery code did `setActiveAccount(null) + clearCache({account}) + loginRedirect()`. When `loginRedirect()` silently failed to navigate (caught by our try/catch — popup blocker, browser policy, async race), the account record was already gone but the tokens lingered. Every subsequent API call hit `getAllAccounts() === []` and silently returned `null` — no Bearer header, endless 401s, only manual `localStorage.clear()` recovered.
|
|
118
|
-
|
|
119
|
-
**Canonical 2026 fix per Microsoft Learn `entra/msal/javascript/browser/errors`** + linked GitHub issues + msal-react samples:
|
|
120
|
-
1. **`acquireTokenRedirect` first, not `loginRedirect`.** It's the documented primitive for refreshing a known account's tokens — preserves the account record across the navigation. `loginRedirect` is only the fallback when `acquireTokenRedirect` itself fails to navigate.
|
|
121
|
-
2. **Never `clearCache` before redirect.** The redirect navigation IS the recovery signal; if it succeeds, MSAL handles state cleanup; if it fails, leave state intact for the next attempt rather than orphaning the cache.
|
|
122
|
-
3. **Detect + repair orphaned state.** When `getAllAccounts() === []` but localStorage has MSAL token entries, nuke ALL `msal.*` keys (localStorage + sessionStorage) and force a fresh `loginRedirect`. Without this, the SPA renders authenticated UI but every API call goes anonymous.
|
|
123
|
-
4. **Module-level recovery deduplication.** Multiple parallel API calls all hitting the recovery path simultaneously now share a single in-flight redirect promise instead of each issuing their own (which would cascade `interaction_in_progress` errors).
|
|
124
|
-
|
|
125
|
-
The previous `recoverFromHardAuthFailure` callback now uses the same primitive — single recovery path for both "MSAL silent failed" and "API rejected refreshed token".
|
|
126
|
-
|
|
127
|
-
68/68 agent tests pass (was 65) — the new tests assert the no-clearCache invariant, the acquireTokenRedirect-first ordering, the loginRedirect fallback path, and the orphaned-state nuke + redirect. Pair: lastspace bump to ^0.1.4.
|
|
128
|
-
|
|
129
|
-
## 0.1.3
|
|
130
|
-
|
|
131
|
-
### Patch Changes
|
|
132
|
-
|
|
133
|
-
- d9b0460: fix(agent): escalate to loginRedirect after second 401
|
|
134
|
-
|
|
135
|
-
Follow-up to the auth-stale-token-401-retry fix in 0.1.2. The first version retried with `forceRefresh: true` and bounced to `loginRedirect` only when MSAL itself reported `InteractionRequiredAuthError`. But there's a more common production failure mode the silent path can't recover: the resource server rejects the **freshly-refreshed** token too — server-side policy drift, audience mismatch, conditional-access re-evaluation, claims challenge, tenant-policy change. MSAL has no way to know about any of this; it produces a clean refreshed token and calls it a day. The user is left in an endless silent 401 loop because nothing kicks them to `loginRedirect`.
|
|
136
|
-
|
|
137
|
-
Adds `recoverFromHardAuthFailure(reason)` on `authStore`. The fetch interceptor calls it when a SECOND consecutive 401 fires (i.e. even the force-refresh retry didn't help). It clears the MSAL cache and starts `loginRedirect` so a brand-new session mints a token bound to current server policy.
|
|
138
|
-
|
|
139
|
-
`createServiceFetch` now accepts an optional `recoverFromHardAuthFailure` callback. The default consumer (`@iloveagents/foundry-web-shell`) wires it to the new method on `authStore`. Same pattern in `tokenFetch`.
|
|
140
|
-
|
|
141
|
-
Pair: `lastspace#TBD` (forwards the new option through `spacesFetch`).
|
|
142
|
-
|
|
143
|
-
## 0.1.2
|
|
144
|
-
|
|
145
|
-
### Patch Changes
|
|
146
|
-
|
|
147
|
-
- 30a4346: fix(agent): retry protected fetch with `forceRefresh` on 401
|
|
148
|
-
|
|
149
|
-
Closes the long-lived-tab 401 loop where the only recovery the user had was logging out or clearing localStorage.
|
|
150
|
-
|
|
151
|
-
The token-fetch wrapper used to ask MSAL for a cached token and never retry. If the resource server rejected that token (claims challenge, conditional-access re-eval, audience drift, or staleness MSAL's clock-offset check missed), every subsequent request kept re-sending the same token.
|
|
152
|
-
|
|
153
|
-
Fix: on 401 from a protected API, retry once with `acquireTokenSilent({ forceRefresh: true })`. If that also fails, the existing `InteractionRequiredAuthError` path bounces the user through Entra ID. Single-retry cap so we never loop. Request bodies are cloned per-attempt (ReadableStream is single-consume) so POST/PUT payloads survive the retry intact.
|
|
154
|
-
|
|
155
|
-
The `acquireToken` callback type now accepts an optional `{ forceRefresh? }` argument — non-breaking; existing callbacks continue to work, but they should forward the option to their token source so the retry path actually reaches the auth backend.
|
|
156
|
-
|
|
157
|
-
Pair: `lastspace#205` (forwards the option through `spacesFetch.acquireToken`).
|
|
158
|
-
|
|
159
|
-
## 0.1.1
|
|
160
|
-
|
|
161
|
-
### Patch Changes
|
|
162
|
-
|
|
163
|
-
- 689d3e9: feat(sidebar): distinct file-drop affordance for nav containers
|
|
164
|
-
|
|
165
|
-
`useNavItemDnd` now exposes `isFileDragOver` separately from `isDragOver`
|
|
166
|
-
so consuming nav items can render a prominent file-drop visual (dashed
|
|
167
|
-
primary outline + soft primary background + Upload icon) when the user
|
|
168
|
-
is dragging native files over the container. Previously file drags
|
|
169
|
-
shared the same subtle ring as entity-move drags, so users couldn't
|
|
170
|
-
tell that a folder accepted external files. Applies to all five nav-
|
|
171
|
-
item shapes (action-row leaf, button leaf, `NestedFolderItem`,
|
|
172
|
-
`CollapsibleNavItem` with children/actions, `NavLink` fallthrough) and
|
|
173
|
-
updates `ContainerDropZone` for visual parity.
|
|
174
|
-
|
|
175
|
-
Also fixes a related regression: the capture-phase
|
|
176
|
-
`onDragEnterCapture` / `onDragOverCapture` handlers were calling
|
|
177
|
-
`e.stopPropagation()`, which short-circuits React's synthetic dispatch
|
|
178
|
-
and prevented the bubble-phase `onDragEnter` (where state actually
|
|
179
|
-
mutates) from running. Removed — `preventDefault()` alone is enough to
|
|
180
|
-
mark the element as droppable.
|
package/CLAUDE.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@AGENTS.md
|