@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 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.21",
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.21",
49
- "@steerable/agent-protocol": "0.6.21"
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.21"
63
+ "@steerable/pack-sdk": "0.6.22"
63
64
  },
64
65
  "scripts": {
65
66
  "gen": "echo agent-shell-web",
@@ -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, LocalChatAgent, LocalTask } from '@/lib/local-api';
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)}
@@ -10,7 +10,7 @@
10
10
  "icon": "Cpu",
11
11
  "color": "#4f46e5",
12
12
  "description": "默认本地助手,可调用 shell、文件与 MCP 工具。",
13
- "rolePrompt": "你是本地离线助手,回答时清晰、可执行。",
13
+ "rolePrompt": "你是 **电脑操作员**,本地离线助手,回答时清晰、可执行。",
14
14
  "forbiddenPrompt": null,
15
15
  "skillIds": [],
16
16
  "toolPolicy": {
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
- export async function bootstrap(): Promise<void> {
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
- createRoot(rootEl).render(
99
- <StrictMode>
100
- <RouterProvider router={router} />
101
- </StrictMode>,
102
- );
124
+ renderApplication(root);
103
125
  }
@@ -1008,7 +1008,6 @@ function AgentChatView({
1008
1008
  header={
1009
1009
  <ChatHeader
1010
1010
  chat={chat}
1011
- agent={agent}
1012
1011
  onBranchSwitched={isElectron() ? onBranchTick : undefined}
1013
1012
  onInspectTask={ctx.inspectTask}
1014
1013
  tasks={tasks}
@@ -242,7 +242,7 @@ export function ChatPanelPreviewPage() {
242
242
  onCancel={() => {}}
243
243
  className="flex-1"
244
244
  header={
245
- <ChatHeader chat={MOCK_CHAT} agent={MOCK_AGENT} />
245
+ <ChatHeader chat={MOCK_CHAT} />
246
246
  }
247
247
  inputPlaceholder="预览模式 — 输入不会真的发送…"
248
248
  agents={[MOCK_AGENT]}