@steerable/agent-shell 0.6.67 → 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.
@@ -24,6 +24,7 @@ import { WorktreeService } from '../local-backend/worktree-service.js';
24
24
  import { TaskService } from '../local-backend/task-service.js';
25
25
  import { LoopPtyMonitor } from '../local-backend/loop-pty-monitor.js';
26
26
  import { LOCAL_SCOPE, closeStorage, getPackDbAccess, getScopedStore, initializeStorage, watchStorageChanges, } from '../storage/driver.js';
27
+ import { subscribeChatActivity } from '../storage/chat-activity.js';
27
28
  import { getUserDataDir } from '../runtime.js';
28
29
  import { acquireInstanceLease, bindStorageInstance, } from '../storage/process-locks.js';
29
30
  import { createApprovalBridge } from '../sidecar/reverse-approval.js';
@@ -45,6 +46,10 @@ export async function createHostRuntime(options) {
45
46
  const broadcastMain = options.broadcastMain ?? broadcast;
46
47
  await initializeStorage();
47
48
  watchStorageChanges(() => broadcast('store:changed', {}));
49
+ // 本连接写入会话不会推动 data_version。活动时间单独广播,侧边栏据此重排。
50
+ const unsubscribeChatActivity = subscribeChatActivity((activity) => {
51
+ broadcast('chat-activity', activity);
52
+ });
48
53
  const scope = options.scope ?? LOCAL_SCOPE;
49
54
  const defaultStore = getScopedStore(scope);
50
55
  let localBackendRouter;
@@ -279,6 +284,7 @@ export async function createHostRuntime(options) {
279
284
  }, 2_000);
280
285
  },
281
286
  async shutdown() {
287
+ unsubscribeChatActivity();
282
288
  if (warmTimer) {
283
289
  clearTimeout(warmTimer);
284
290
  warmTimer = null;
@@ -247,7 +247,10 @@ export function createBsServer(deps) {
247
247
  sendJson(res, 200, { ok: false, reason: 'no_active_coreloop_turn' });
248
248
  return;
249
249
  }
250
- const ok = await supervisor.steerChat(streamId, content);
250
+ const execPolicy = body.execPolicy === 'full' || body.execPolicy === 'workspace'
251
+ ? body.execPolicy
252
+ : undefined;
253
+ const ok = await supervisor.steerChat(streamId, content, execPolicy);
251
254
  // 与 main.ts 同理:接受即落库,重开对话能看到这条注入的用户消息。
252
255
  if (ok)
253
256
  await deps.store.addMessage(chatId, 'user', content);
@@ -152,7 +152,7 @@ export declare class SidecarSupervisor extends EventEmitter {
152
152
  * Soft-fails (`{ ok: false }`) when the turn already ended — the caller
153
153
  * should then send the message as a normal new turn instead.
154
154
  */
155
- steerChat(streamId: string, content: string): Promise<boolean>;
155
+ steerChat(streamId: string, content: string, execPolicy?: 'workspace' | 'full'): Promise<boolean>;
156
156
  /**
157
157
  * Register a host-side handler for a reverse (sidecar -> host) request.
158
158
  *
@@ -310,9 +310,13 @@ export class SidecarSupervisor extends EventEmitter {
310
310
  * Soft-fails (`{ ok: false }`) when the turn already ended — the caller
311
311
  * should then send the message as a normal new turn instead.
312
312
  */
313
- async steerChat(streamId, content) {
313
+ async steerChat(streamId, content, execPolicy) {
314
314
  try {
315
- const result = await this.call('agent.chat.steer', { streamId, content }, { timeoutMs: 2_000 });
315
+ const result = await this.call('agent.chat.steer', {
316
+ streamId,
317
+ content,
318
+ ...(execPolicy ? { execPolicy } : {}),
319
+ }, { timeoutMs: 2_000 });
316
320
  return result.ok === true;
317
321
  }
318
322
  catch {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 会话最后活动时间变了之后通知宿主。
3
+ * SQLite 的 data_version 看不到本连接自己的提交,所以不能靠 store:changed
4
+ * 把这次更新送到侧边栏。
5
+ */
6
+ export interface ChatActivity {
7
+ chatId: string;
8
+ updatedAt: string;
9
+ }
10
+ type ChatActivityListener = (activity: ChatActivity) => void;
11
+ /** 订阅会话活动。返回解除订阅。 */
12
+ export declare function subscribeChatActivity(listener: ChatActivityListener): () => void;
13
+ /** 通知所有订阅者。没有订阅者时不做任何事。 */
14
+ export declare function emitChatActivity(activity: ChatActivity): void;
15
+ export {};
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 会话最后活动时间变了之后通知宿主。
3
+ * SQLite 的 data_version 看不到本连接自己的提交,所以不能靠 store:changed
4
+ * 把这次更新送到侧边栏。
5
+ */
6
+ const listeners = new Set();
7
+ /** 订阅会话活动。返回解除订阅。 */
8
+ export function subscribeChatActivity(listener) {
9
+ listeners.add(listener);
10
+ return () => {
11
+ listeners.delete(listener);
12
+ };
13
+ }
14
+ /** 通知所有订阅者。没有订阅者时不做任何事。 */
15
+ export function emitChatActivity(activity) {
16
+ for (const listener of listeners)
17
+ listener(activity);
18
+ }
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { ALL_ROUND_ASSISTANT_AGENT_ID, LOCAL_ASSISTANT_AGENT_ID, getBrand, } from '../brand.js';
4
4
  import { isShellBuiltinAgentEnabled } from '../product-config.js';
5
5
  import { DEFAULT_LLM_SETTINGS, llmSettingsCarryExpiredBakedKey, llmSettingsMatchOllamaDefaults, mergeLlmSettings, } from './llm-settings.js';
6
+ import { emitChatActivity } from './chat-activity.js';
6
7
  import { MESSAGE_ORDER_DESC } from './message-order.js';
7
8
  import { currentStorageInstanceId, instanceOwnerIsLive, } from './process-locks.js';
8
9
  import { mergeTelemetrySettings, } from './telemetry-settings.js';
@@ -727,6 +728,7 @@ export class SqliteScopedStore {
727
728
  SET title = ?, system_prompt = ?, pinned_refs = ?, is_pinned = ?, project_id = ?, updated_at = ?
728
729
  WHERE tenant_id = ? AND user_id = ? AND id = ?
729
730
  `).run(next.title, next.systemPrompt, JSON.stringify(next.pinnedRefs ?? null), next.isPinned ? 1 : 0, next.projectId ?? null, next.updatedAt, this.scope.tenantId, this.scope.userId, chatId);
731
+ emitChatActivity({ chatId, updatedAt: next.updatedAt });
730
732
  return await this.getChat(chatId);
731
733
  }
732
734
  async deleteChat(chatId) {
@@ -867,8 +869,10 @@ export class SqliteScopedStore {
867
869
  (tenant_id, user_id, id, chat_id, role, content, message_metadata, created_at)
868
870
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
869
871
  `).run(this.scope.tenantId, this.scope.userId, id, chatId, role, content, messageMetadata, now);
870
- this.db.prepare(`UPDATE chat_sessions SET updated_at = ?
872
+ const updated = this.db.prepare(`UPDATE chat_sessions SET updated_at = ?
871
873
  WHERE tenant_id = ? AND user_id = ? AND id = ?`).run(now, this.scope.tenantId, this.scope.userId, chatId);
874
+ if (updated.changes > 0)
875
+ emitChatActivity({ chatId, updatedAt: now });
872
876
  const row = this.db.prepare(`SELECT * FROM chat_messages WHERE tenant_id = ? AND user_id = ? AND id = ?`).get(this.scope.tenantId, this.scope.userId, id);
873
877
  return this.mapChatMessage(row);
874
878
  }
@@ -910,6 +914,7 @@ export class SqliteScopedStore {
910
914
  */
911
915
  async replaceChatMessages(chatId, messages) {
912
916
  const base = Date.now();
917
+ let activity = null;
913
918
  const insert = this.db.prepare(`
914
919
  INSERT INTO chat_messages
915
920
  (tenant_id, user_id, id, chat_id, role, content, message_metadata, created_at)
@@ -920,11 +925,16 @@ export class SqliteScopedStore {
920
925
  messages.forEach((m, i) => {
921
926
  insert.run(this.scope.tenantId, this.scope.userId, randomUUID(), chatId, m.role, m.content, new Date(base + i).toISOString());
922
927
  });
923
- this.db
928
+ const updatedAt = new Date(base + messages.length).toISOString();
929
+ const updated = this.db
924
930
  .prepare(`UPDATE chat_sessions SET updated_at = ?
925
931
  WHERE tenant_id = ? AND user_id = ? AND id = ?`)
926
- .run(new Date(base + messages.length).toISOString(), this.scope.tenantId, this.scope.userId, chatId);
932
+ .run(updatedAt, this.scope.tenantId, this.scope.userId, chatId);
933
+ if (updated.changes > 0)
934
+ activity = { chatId, updatedAt };
927
935
  })();
936
+ if (activity)
937
+ emitChatActivity(activity);
928
938
  }
929
939
  async listChatAgents(includeArchived = false) {
930
940
  const rows = this.db.prepare(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steerable/agent-shell",
3
- "version": "0.6.67",
3
+ "version": "0.6.69",
4
4
  "description": "Steerable framework — product-neutral Node HostRuntime and headless HTTP server shared by Tauri desktop and browser deployments; scenario packs extend it through @steerable/pack-sdk.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://steerableframework.com/",
@@ -53,9 +53,9 @@
53
53
  "jpeg-js": "^0.4.4",
54
54
  "node-pty": "^1.1.0",
55
55
  "pngjs": "^7.0.0",
56
- "@steerable/agent-harness": "0.6.67",
57
- "@steerable/agent-protocol": "0.6.67",
58
- "@steerable/pack-sdk": "0.6.67"
56
+ "@steerable/agent-harness": "0.6.69",
57
+ "@steerable/agent-protocol": "0.6.69",
58
+ "@steerable/pack-sdk": "0.6.69"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/better-sqlite3": "^7.6.13",