@steerable/agent-shell-web 0.6.21 → 0.6.22
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 +6 -0
- package/package.json +5 -4
- package/src/auth/gate.ts +26 -0
- package/src/components/ChatHeader.tsx +1 -17
- package/src/fixtures/browser-dev-data.json +1 -1
- package/src/main.tsx +41 -19
- package/src/pages/AgentPage.tsx +0 -1
- package/src/pages/ChatPanelPreviewPage.tsx +1 -1
package/README.md
CHANGED
|
@@ -17,4 +17,10 @@ bundle; there is no prebuilt `dist/` here. See
|
|
|
17
17
|
[`docs/spec/architecture.md`](https://github.com/pathlyapp/steerable-framework/blob/develop/docs/spec/architecture.md)
|
|
18
18
|
for the tier model.
|
|
19
19
|
|
|
20
|
+
Products that require authentication can register an `AppShellGate` from
|
|
21
|
+
`@steerable/agent-shell-web/auth/gate` before calling `bootstrap()`. An
|
|
22
|
+
enabled gate renders before the application router and calls
|
|
23
|
+
`onAuthenticated` to continue normal bootstrap. With no gate, or a disabled
|
|
24
|
+
gate, startup is unchanged.
|
|
25
|
+
|
|
20
26
|
License: Apache-2.0.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-shell-web",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.22",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Steerable framework — product-neutral renderer SPA source for @steerable/agent-shell (React + Vite). Consumed by product web entry points via the `@/` alias (anchored at this package's src/) and the createProductViteConfig factory. Published as a source package (src/), plus the compiled vite.base config factory (vite.base.js + .d.ts) for Node-side config loading.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"url": "https://github.com/pathlyapp/steerable-framework/issues"
|
|
15
15
|
},
|
|
16
16
|
"exports": {
|
|
17
|
+
"./auth/gate": "./src/auth/gate.ts",
|
|
17
18
|
"./vite.base": {
|
|
18
19
|
"types": "./vite.base.d.ts",
|
|
19
20
|
"default": "./vite.base.js"
|
|
@@ -45,8 +46,8 @@
|
|
|
45
46
|
"react-router-dom": "^6.30.0",
|
|
46
47
|
"rehype-raw": "^7.0.0",
|
|
47
48
|
"remark-gfm": "^4.0.1",
|
|
48
|
-
"@steerable/agent-ui": "0.6.
|
|
49
|
-
"@steerable/agent-protocol": "0.6.
|
|
49
|
+
"@steerable/agent-ui": "0.6.22",
|
|
50
|
+
"@steerable/agent-protocol": "0.6.22"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@tailwindcss/vite": "^4.3.0",
|
|
@@ -59,7 +60,7 @@
|
|
|
59
60
|
"typescript": "^5.8.3",
|
|
60
61
|
"vite": "^5.4.21",
|
|
61
62
|
"vitest": "^3.2.4",
|
|
62
|
-
"@steerable/pack-sdk": "0.6.
|
|
63
|
+
"@steerable/pack-sdk": "0.6.22"
|
|
63
64
|
},
|
|
64
65
|
"scripts": {
|
|
65
66
|
"gen": "echo agent-shell-web",
|
package/src/auth/gate.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Optional product-provided gate rendered before the application router.
|
|
5
|
+
*/
|
|
6
|
+
export interface AppShellGate {
|
|
7
|
+
/** Allows one product entry to disable its gate for unsupported runtimes. */
|
|
8
|
+
enabled(): boolean;
|
|
9
|
+
/** Renders authentication UI and resumes bootstrap through the callback. */
|
|
10
|
+
Component: ComponentType<{ onAuthenticated: () => void }>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let registeredGate: AppShellGate | null = null;
|
|
14
|
+
|
|
15
|
+
/** Registers the app-shell gate before calling `bootstrap()`. */
|
|
16
|
+
export function registerAppShellGate(gate: AppShellGate): void {
|
|
17
|
+
if (registeredGate) {
|
|
18
|
+
throw new Error('[app-shell-gate] a gate is already registered');
|
|
19
|
+
}
|
|
20
|
+
registeredGate = gate;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Returns the product-provided gate, if one was registered. */
|
|
24
|
+
export function getAppShellGate(): AppShellGate | null {
|
|
25
|
+
return registeredGate;
|
|
26
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
3
|
import { LuCheck, LuGitBranch, LuListTodo, LuListTree, LuLoaderCircle } from 'react-icons/lu';
|
|
4
|
-
import type { LocalChat,
|
|
4
|
+
import type { LocalChat, LocalTask } from '@/lib/local-api';
|
|
5
5
|
import {
|
|
6
6
|
activateChatBranch,
|
|
7
7
|
getChatBranches,
|
|
@@ -13,7 +13,6 @@ import { actionableTasks, summarizeTasks, type ChatTaskSummary } from './chat/us
|
|
|
13
13
|
|
|
14
14
|
interface ChatHeaderProps {
|
|
15
15
|
chat: LocalChat | null;
|
|
16
|
-
agent: LocalChatAgent | null;
|
|
17
16
|
/**
|
|
18
17
|
* W1.2.1: called after the active branch switches — the page re-hydrates
|
|
19
18
|
* the message list from the re-projected store. When provided (and the
|
|
@@ -29,8 +28,6 @@ interface ChatHeaderProps {
|
|
|
29
28
|
/**
|
|
30
29
|
* ChatHeader — slim title bar above ChatPanel. Shows:
|
|
31
30
|
* - chat title (完整显示,长标题换行不截断)
|
|
32
|
-
* - bound agent name + icon (read-only here; switching is done from the
|
|
33
|
-
* sidebar's expert team list)
|
|
34
31
|
* - shortened chat id (for support / debugging)
|
|
35
32
|
*
|
|
36
33
|
* Terminal / 场景包调试窗 / 本地模型设置 USED to live here, but per the
|
|
@@ -40,7 +37,6 @@ interface ChatHeaderProps {
|
|
|
40
37
|
*/
|
|
41
38
|
export function ChatHeader({
|
|
42
39
|
chat,
|
|
43
|
-
agent,
|
|
44
40
|
onBranchSwitched,
|
|
45
41
|
onInspectTask,
|
|
46
42
|
tasks = [],
|
|
@@ -186,18 +182,6 @@ export function ChatHeader({
|
|
|
186
182
|
>
|
|
187
183
|
{chat?.title ?? '未选择对话'}
|
|
188
184
|
</span>
|
|
189
|
-
<span className="shrink-0 text-xs text-agent-muted-foreground">
|
|
190
|
-
{agent ? (
|
|
191
|
-
<>
|
|
192
|
-
<span className="mr-0.5">{agent.icon || '🤖'}</span>
|
|
193
|
-
{agent.name}
|
|
194
|
-
</>
|
|
195
|
-
) : chat?.agentId ? (
|
|
196
|
-
<span className="font-mono">{chat.agentId}</span>
|
|
197
|
-
) : (
|
|
198
|
-
<span className="italic">no agent</span>
|
|
199
|
-
)}
|
|
200
|
-
</span>
|
|
201
185
|
{chat && (
|
|
202
186
|
<span className="hidden shrink-0 font-mono text-[10px] text-agent-muted-foreground/70 md:inline">
|
|
203
187
|
{shortenId(chat.id)}
|
package/src/main.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StrictMode, lazy, Suspense, type ReactNode } from 'react';
|
|
2
|
-
import { createRoot } from 'react-dom/client';
|
|
2
|
+
import { createRoot, type Root } from 'react-dom/client';
|
|
3
3
|
import { createHashRouter, RouterProvider } from 'react-router-dom';
|
|
4
4
|
import { AppShell } from './AppShell';
|
|
5
5
|
import { AgentLayout } from './layouts/AgentLayout';
|
|
@@ -7,6 +7,7 @@ import { AgentPage } from './pages/AgentPage';
|
|
|
7
7
|
import { SettingsPage } from './pages/SettingsPage';
|
|
8
8
|
import { getPackRoutes } from './packs/registry';
|
|
9
9
|
import { getBrandLogoUrl, BRAND_NAME } from './brand';
|
|
10
|
+
import { getAppShellGate } from './auth/gate';
|
|
10
11
|
import './styles/index.css';
|
|
11
12
|
|
|
12
13
|
// 浏览器 dev mock 的安装在 bootstrap() 开头(动态 import + DEV 门)。
|
|
@@ -40,19 +41,7 @@ const ChatPanelPreviewPage = import.meta.env.DEV
|
|
|
40
41
|
* (products/<id>/web/main.tsx)先静态调用包的 register*Renderer(),
|
|
41
42
|
* 再调本函数——路由注册发生在路由表创建之前。
|
|
42
43
|
*/
|
|
43
|
-
|
|
44
|
-
if (import.meta.env.DEV) {
|
|
45
|
-
// 动态 import:prod 构建里 Rollup 把 DEV 分支连同 mock(含其 fixtures)
|
|
46
|
-
// 整体树摇掉,浏览器 dev mock 数据不进产物。
|
|
47
|
-
const { installBrowserDevElectronMock } = await import('./lib/browser-dev-electron-mock');
|
|
48
|
-
installBrowserDevElectronMock();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// 标题与 favicon 在 bootstrap 时设置——此刻产品入口已完成包注册
|
|
52
|
-
// (品牌 logo 由包经 setBrandLogoUrl 注入,见 brand.ts)。
|
|
53
|
-
document.title = BRAND_NAME;
|
|
54
|
-
document.querySelector<HTMLLinkElement>('link[rel="icon"]')?.setAttribute('href', getBrandLogoUrl());
|
|
55
|
-
|
|
44
|
+
function renderApplication(root: Root): void {
|
|
56
45
|
const router = createHashRouter([
|
|
57
46
|
{
|
|
58
47
|
path: '/',
|
|
@@ -90,14 +79,47 @@ export async function bootstrap(): Promise<void> {
|
|
|
90
79
|
},
|
|
91
80
|
]);
|
|
92
81
|
|
|
82
|
+
root.render(
|
|
83
|
+
<StrictMode>
|
|
84
|
+
<RouterProvider router={router} />
|
|
85
|
+
</StrictMode>,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function bootstrap(): Promise<void> {
|
|
90
|
+
if (import.meta.env.DEV) {
|
|
91
|
+
// 动态 import:prod 构建里 Rollup 把 DEV 分支连同 mock(含其 fixtures)
|
|
92
|
+
// 整体树摇掉,浏览器 dev mock 数据不进产物。
|
|
93
|
+
const { installBrowserDevElectronMock } = await import('./lib/browser-dev-electron-mock');
|
|
94
|
+
installBrowserDevElectronMock();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 标题与 favicon 在 bootstrap 时设置——此刻产品入口已完成包注册
|
|
98
|
+
// (品牌 logo 由包经 setBrandLogoUrl 注入,见 brand.ts)。
|
|
99
|
+
document.title = BRAND_NAME;
|
|
100
|
+
document.querySelector<HTMLLinkElement>('link[rel="icon"]')?.setAttribute('href', getBrandLogoUrl());
|
|
101
|
+
|
|
93
102
|
const rootEl = document.getElementById('root');
|
|
94
103
|
if (!rootEl) {
|
|
95
104
|
throw new Error('Missing #root element in index.html');
|
|
96
105
|
}
|
|
106
|
+
const root = createRoot(rootEl);
|
|
107
|
+
const gate = getAppShellGate();
|
|
108
|
+
if (gate?.enabled()) {
|
|
109
|
+
let authenticated = false;
|
|
110
|
+
const onAuthenticated = () => {
|
|
111
|
+
if (authenticated) return;
|
|
112
|
+
authenticated = true;
|
|
113
|
+
renderApplication(root);
|
|
114
|
+
};
|
|
115
|
+
const Gate = gate.Component;
|
|
116
|
+
root.render(
|
|
117
|
+
<StrictMode>
|
|
118
|
+
<Gate onAuthenticated={onAuthenticated} />
|
|
119
|
+
</StrictMode>,
|
|
120
|
+
);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
97
123
|
|
|
98
|
-
|
|
99
|
-
<StrictMode>
|
|
100
|
-
<RouterProvider router={router} />
|
|
101
|
-
</StrictMode>,
|
|
102
|
-
);
|
|
124
|
+
renderApplication(root);
|
|
103
125
|
}
|
package/src/pages/AgentPage.tsx
CHANGED
|
@@ -242,7 +242,7 @@ export function ChatPanelPreviewPage() {
|
|
|
242
242
|
onCancel={() => {}}
|
|
243
243
|
className="flex-1"
|
|
244
244
|
header={
|
|
245
|
-
<ChatHeader chat={MOCK_CHAT}
|
|
245
|
+
<ChatHeader chat={MOCK_CHAT} />
|
|
246
246
|
}
|
|
247
247
|
inputPlaceholder="预览模式 — 输入不会真的发送…"
|
|
248
248
|
agents={[MOCK_AGENT]}
|