@steerable/agent-shell-web 0.6.68 → 0.6.69

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steerable/agent-shell-web",
3
- "version": "0.6.68",
3
+ "version": "0.6.69",
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",
@@ -45,8 +45,8 @@
45
45
  "react-router-dom": "^6.30.0",
46
46
  "rehype-raw": "^7.0.0",
47
47
  "remark-gfm": "^4.0.1",
48
- "@steerable/agent-protocol": "0.6.68",
49
- "@steerable/agent-ui": "0.6.68"
48
+ "@steerable/agent-protocol": "0.6.69",
49
+ "@steerable/agent-ui": "0.6.69"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@tailwindcss/vite": "^4.3.0",
@@ -59,7 +59,7 @@
59
59
  "typescript": "^5.8.3",
60
60
  "vite": "^5.4.21",
61
61
  "vitest": "^3.2.4",
62
- "@steerable/pack-sdk": "0.6.68"
62
+ "@steerable/pack-sdk": "0.6.69"
63
63
  },
64
64
  "scripts": {
65
65
  "gen": "echo agent-shell-web",
@@ -6,7 +6,8 @@ import { t } from '@/i18n';
6
6
  /**
7
7
  * ExecPolicyPicker — 输入框上的命令沙箱切换(类 Codex 底部权限档)。
8
8
  *
9
- * 切档立刻记住,下一轮才生效:当前回合的 `execSandbox` 已经随流发出。
9
+ * 切档立刻记住。排队的下一轮按当时的档开流;插进当前回合的消息会改
10
+ * 这一回合后面的命令沙箱。已经在跑的那条命令保持它开始时的档。
10
11
  * 工作区 = Seatbelt/bwrap 只允许写项目家目录、源文件夹(含各自子目录)或本对话工作区;完整权限
11
12
  * = 不下发命令沙箱,本机路径(如 Downloads)不再被 Operation not permitted 拦住。
12
13
  */
@@ -254,10 +254,14 @@ export function createHostChatTransport(chatId: string): HostChatTransport {
254
254
  },
255
255
  // 轮中转向:仅在 CoreLoop 路径(STEERABLE_USE_CORELOOP=1)且回合仍
256
256
  // 运行时生效;否则主进程软失败,useChatStream 保留草稿。
257
- steer: async (content: string) => {
257
+ steer: async (content: string, options?: { execPolicy?: 'workspace' | 'full' }) => {
258
258
  const bridge = getHostBridge();
259
259
  if (!bridge?.localBackend.steerChat) return false;
260
- return await bridge.localBackend.steerChat(chatId, content);
260
+ return await bridge.localBackend.steerChat(
261
+ chatId,
262
+ content,
263
+ options?.execPolicy ? { execPolicy: options.execPolicy } : undefined,
264
+ );
261
265
  },
262
266
  stream: async (input: ChatStreamSendInput, onEvent) => {
263
267
  const bridge = getHostBridge();
@@ -159,8 +159,15 @@ export interface HostBridge {
159
159
  onEvent: (payload: LocalBackendStreamEvent) => void,
160
160
  ) => Promise<string | null>;
161
161
  cancelStream: (streamId: string) => void;
162
- /** 轮中转向:注入一条用户消息到运行中的 CoreLoop 回合;false = 无可转向回合。 */
163
- steerChat?: (chatId: string, content: string) => Promise<boolean>;
162
+ /**
163
+ * 轮中转向:注入一条用户消息到运行中的 CoreLoop 回合;false = 无可转向回合。
164
+ * `execPolicy` 改这一回合后面的命令沙箱(工作区 / 完整权限)。
165
+ */
166
+ steerChat?: (
167
+ chatId: string,
168
+ content: string,
169
+ options?: { execPolicy?: 'workspace' | 'full' },
170
+ ) => Promise<boolean>;
164
171
  };
165
172
  /** 会话附件:把源文件路径 / base64 字节拷贝进会话空间(`attachments:save`)。 */
166
173
  attachments?: {
@@ -257,8 +257,12 @@ export function createHttpBridge(): HostBridge {
257
257
  http<T>(input.method, input.path, input.body),
258
258
  startStream,
259
259
  cancelStream,
260
- steerChat: async (chatId, content) => {
261
- const result = await http<{ ok?: boolean }>('POST', '/host/steer', { chatId, content });
260
+ steerChat: async (chatId, content, options) => {
261
+ const result = await http<{ ok?: boolean }>('POST', '/host/steer', {
262
+ chatId,
263
+ content,
264
+ ...(options?.execPolicy ? { execPolicy: options.execPolicy } : {}),
265
+ });
262
266
  return result.ok === true;
263
267
  },
264
268
  },
@@ -101,6 +101,31 @@ function readStoredMode(): ChatMode {
101
101
  return clampWebChatMode(localStorage.getItem(CHAT_MODE_STORAGE_KEY));
102
102
  }
103
103
 
104
+ interface ComposerTurnSettings {
105
+ execPolicy: ExecPolicy;
106
+ mode: ChatMode;
107
+ modelOverride: string | null;
108
+ effortOverride: string | null;
109
+ }
110
+
111
+ /**
112
+ * 排队和插队失败后的新回合不经过输入框的正常提交,这里在真正开流时
113
+ * 补上当前输入框的命令沙箱、模式、模型和编排开关。
114
+ */
115
+ function applyComposerTurnSettings<T extends { metadata?: Record<string, unknown> }>(
116
+ input: T,
117
+ settings: ComposerTurnSettings,
118
+ ): T {
119
+ const metadata: Record<string, unknown> = { ...(input.metadata ?? {}) };
120
+ metadata.execPolicy = settings.execPolicy;
121
+ if (settings.mode === "plan") metadata.mode = "plan";
122
+ else delete metadata.mode;
123
+ if (settings.modelOverride) metadata.model = settings.modelOverride;
124
+ if (settings.effortOverride) metadata.reasoningEffort = settings.effortOverride;
125
+ metadata.orchestration = isOrchestrationSettingEnabled();
126
+ return { ...input, metadata };
127
+ }
128
+
104
129
  function extractPersistedActions(
105
130
  messages: ChatMessageWithMetadata[] | undefined,
106
131
  ): Record<string, ExecutedAction[]> {
@@ -388,10 +413,29 @@ function AgentChatView({
388
413
  const chatProject = chat?.projectId
389
414
  ? (projects.find((p) => p.id === chat.projectId) ?? null)
390
415
  : null;
391
- const transport = useMemo(
392
- () => createHostChatTransport(chatId),
393
- [chatId],
394
- );
416
+ // 插队 / 排队在开流或注入时读这里,而不是排队那一刻的闭包。
417
+ const composerSettingsRef = useRef<ComposerTurnSettings>({
418
+ execPolicy: readStoredExecPolicy(),
419
+ mode: readStoredMode(),
420
+ modelOverride: null,
421
+ effortOverride: null,
422
+ });
423
+ const transport = useMemo(() => {
424
+ const inner = createHostChatTransport(chatId);
425
+ return {
426
+ cancelActive: () => inner.cancelActive(),
427
+ stream: (
428
+ input: { content: string; metadata?: Record<string, unknown> },
429
+ onEvent: (event: SSEEvent) => void,
430
+ ) => inner.stream(applyComposerTurnSettings(input, composerSettingsRef.current), onEvent),
431
+ steer: (content: string) => {
432
+ if (!inner.steer) return Promise.resolve(false);
433
+ return inner.steer(content, {
434
+ execPolicy: composerSettingsRef.current.execPolicy,
435
+ });
436
+ },
437
+ };
438
+ }, [chatId]);
395
439
 
396
440
  // 4.6a 后台任务:header 角标与消息列尾部的终态卡共用这一份订阅,两处
397
441
  // 因此不会各拉一次任务表、也不会显示互相错位的状态。
@@ -874,6 +918,12 @@ function AgentChatView({
874
918
  // ModelPicker 自取(GET /api/v2/llm/models → sidecar models.list)。
875
919
  const [modelOverride, setModelOverride] = useState<string | null>(null);
876
920
  const [effortOverride, setEffortOverride] = useState<string | null>(null);
921
+ composerSettingsRef.current = {
922
+ execPolicy,
923
+ mode,
924
+ modelOverride,
925
+ effortOverride,
926
+ };
877
927
 
878
928
  const handleSubmit = useCallback(
879
929
  (input: { content: string; metadata?: Record<string, unknown> }) => {