@steerable/agent-shell 0.6.59 → 0.6.61

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.
@@ -0,0 +1,27 @@
1
+ export interface StoredTurn {
2
+ id: string;
3
+ role: 'user' | 'assistant' | 'system' | 'tool';
4
+ content: string;
5
+ messageMetadata?: string | null;
6
+ }
7
+ export interface HistoryWrite {
8
+ role: StoredTurn['role'];
9
+ content: string;
10
+ messageMetadata: string | null;
11
+ }
12
+ /**
13
+ * Newest-first history. The newest two messages stay. Older ones become one summary,
14
+ * and the caller deletes from the oldest id (which removes the whole chat) before writing these back.
15
+ */
16
+ export declare function compactHistory(messages: readonly StoredTurn[]): {
17
+ deleteFromId: string;
18
+ writes: HistoryWrite[];
19
+ compacted: number;
20
+ } | null;
21
+ /** Newest-first. Cut from the newest user message through the reply after it. */
22
+ export declare function rewindFrom(messages: readonly StoredTurn[]): {
23
+ deleteFromId: string;
24
+ removed: number;
25
+ } | null;
26
+ /** Newest-first history copied in time order. */
27
+ export declare function forkHistory(messages: readonly StoredTurn[]): HistoryWrite[];
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Newest-first history. The newest two messages stay. Older ones become one summary,
3
+ * and the caller deletes from the oldest id (which removes the whole chat) before writing these back.
4
+ */
5
+ export function compactHistory(messages) {
6
+ if (messages.length <= 2)
7
+ return null;
8
+ const kept = messages.slice(0, 2);
9
+ const older = messages.slice(2);
10
+ const oldest = older[older.length - 1];
11
+ if (!oldest)
12
+ return null;
13
+ const summary = older.slice().reverse().map((message) => {
14
+ const text = message.content.replace(/\s+/g, ' ').trim();
15
+ const clipped = text.length > 80 ? `${text.slice(0, 80)}…` : text;
16
+ return `${message.role}: ${clipped}`;
17
+ }).join('\n');
18
+ return {
19
+ deleteFromId: oldest.id,
20
+ compacted: older.length,
21
+ writes: [
22
+ { role: 'assistant', content: `已压缩 ${older.length} 条\n${summary}`, messageMetadata: null },
23
+ ...kept.slice().reverse().map((message) => ({
24
+ role: message.role,
25
+ content: message.content,
26
+ messageMetadata: message.messageMetadata ?? null,
27
+ })),
28
+ ],
29
+ };
30
+ }
31
+ /** Newest-first. Cut from the newest user message through the reply after it. */
32
+ export function rewindFrom(messages) {
33
+ const index = messages.findIndex((message) => message.role === 'user');
34
+ const target = index >= 0 ? messages[index] : undefined;
35
+ if (!target)
36
+ return null;
37
+ return { deleteFromId: target.id, removed: index + 1 };
38
+ }
39
+ /** Newest-first history copied in time order. */
40
+ export function forkHistory(messages) {
41
+ return messages.slice().reverse().map((message) => ({
42
+ role: message.role,
43
+ content: message.content,
44
+ messageMetadata: message.messageMetadata ?? null,
45
+ }));
46
+ }
@@ -192,6 +192,7 @@ export declare class LocalBackendRouter {
192
192
  * 用 parseSSEData 把 data 反序列化为 JSON 后用 type/content 字段路由。
193
193
  */
194
194
  private sseData;
195
+ private historyTurns;
195
196
  private toRecord;
196
197
  private safeJson;
197
198
  private notFound;
@@ -48,6 +48,7 @@ import { chatAttachmentsDirPath } from '../attachments.js';
48
48
  import { loadProjectRuleFiles } from '../project-rules.js';
49
49
  import { allocateProjectHome, ensureChatWorkspace, ensureProjectHome, expandUserPath, } from '../project-home.js';
50
50
  import { registerLiveStream, getLiveStream, removeLiveStream } from './live-stream.js';
51
+ import { compactHistory, forkHistory, rewindFrom } from './history-edit.js';
51
52
  import { beginPackTurnObservers, collectPackExecWritableRoots, collectPackForcedSkillVars, collectPackWorldState, } from './pack-turn-hooks.js';
52
53
  import { matchPackBackendRoute } from './pack-backend-routes.js';
53
54
  import { invokePackHttpRoute } from '../host/http-routes.js';
@@ -870,6 +871,45 @@ export class LocalBackendRouter {
870
871
  await supervisor.cancelChat(streamId);
871
872
  return { status: 200, data: { success: true } };
872
873
  }
874
+ const compactMatch = pathname.match(/^\/api\/v2\/chats\/([^/]+)\/compact$/);
875
+ if (compactMatch && method === 'POST') {
876
+ const chatId = compactMatch[1];
877
+ const chat = await this.store.getChat(chatId);
878
+ if (!chat)
879
+ return { status: 404, data: { detail: 'chat not found' } };
880
+ const plan = compactHistory(await this.historyTurns(chatId));
881
+ if (!plan)
882
+ return { status: 200, data: { compacted: 0 } };
883
+ await this.store.deleteMessagesFrom(chatId, plan.deleteFromId);
884
+ for (const write of plan.writes) {
885
+ await this.store.addMessage(chatId, write.role, write.content, write.messageMetadata);
886
+ }
887
+ return { status: 200, data: { compacted: plan.compacted } };
888
+ }
889
+ const forkMatch = pathname.match(/^\/api\/v2\/chats\/([^/]+)\/fork$/);
890
+ if (forkMatch && method === 'POST') {
891
+ const chatId = forkMatch[1];
892
+ const chat = await this.store.getChat(chatId);
893
+ if (!chat)
894
+ return { status: 404, data: { detail: 'chat not found' } };
895
+ const created = await this.store.createChat(chat.title || '分叉', chat.agentId, chat.projectId);
896
+ for (const write of forkHistory(await this.historyTurns(chatId))) {
897
+ await this.store.addMessage(created.id, write.role, write.content, write.messageMetadata);
898
+ }
899
+ return { status: 200, data: { chatId: created.id } };
900
+ }
901
+ const rewindMatch = pathname.match(/^\/api\/v2\/chats\/([^/]+)\/rewind$/);
902
+ if (rewindMatch && method === 'POST') {
903
+ const chatId = rewindMatch[1];
904
+ const chat = await this.store.getChat(chatId);
905
+ if (!chat)
906
+ return { status: 404, data: { detail: 'chat not found' } };
907
+ const cut = rewindFrom(await this.historyTurns(chatId));
908
+ if (!cut)
909
+ return { status: 200, data: { removed: 0 } };
910
+ const removed = await this.store.deleteMessagesFrom(chatId, cut.deleteFromId);
911
+ return { status: 200, data: { removed } };
912
+ }
873
913
  if (method === 'GET' && pathname === '/api/v2/chat-agents') {
874
914
  const includeArchived = url.searchParams.get('include_archived') === 'true';
875
915
  const agents = await this.store.listChatAgents(includeArchived);
@@ -3556,6 +3596,15 @@ export class LocalBackendRouter {
3556
3596
  sseData(data) {
3557
3597
  return `data: ${JSON.stringify(data)}\n\n`;
3558
3598
  }
3599
+ async historyTurns(chatId) {
3600
+ const messages = await this.store.listMessages(chatId);
3601
+ return messages.map((message) => ({
3602
+ id: message.id,
3603
+ role: message.role,
3604
+ content: message.content,
3605
+ messageMetadata: message.messageMetadata,
3606
+ }));
3607
+ }
3559
3608
  toRecord(body) {
3560
3609
  if (!body || typeof body !== 'object')
3561
3610
  return {};
@@ -147,6 +147,7 @@ export function downloadPublishedEgress(repoRoot, cacheDir) {
147
147
  const child = spawn(python, [script, 'egress', '--artifact-lock', '--target', 'host', '--out', cacheDir], {
148
148
  cwd: repoRoot,
149
149
  stdio: ['ignore', 'pipe', 'pipe'],
150
+ windowsHide: true,
150
151
  });
151
152
  const timer = setTimeout(() => child.kill(), EGRESS_DOWNLOAD_TIMEOUT_MS);
152
153
  let stdout = '';
@@ -428,6 +429,7 @@ export async function allowEgressForBaseUrl(baseUrl) {
428
429
  export async function startEgressProxy(plan, onLog, readyTimeoutMs = 10_000) {
429
430
  const child = spawn(plan.command, plan.args, {
430
431
  stdio: ['ignore', 'pipe', 'pipe'],
432
+ windowsHide: true,
431
433
  // 凭证与控制 token 经 env 传入代理进程——argv 在 ps 里可见,env 不入
432
434
  // 命令行。
433
435
  env: plan.broker
@@ -389,6 +389,7 @@ export class SidecarSupervisor extends EventEmitter {
389
389
  cwd: this.options.cwd,
390
390
  env: { ...process.env, ...this.options.env, ...spawnPlan.env },
391
391
  stdio: ['pipe', 'pipe', 'pipe'],
392
+ windowsHide: true,
392
393
  });
393
394
  this.child = child;
394
395
  this.attachListeners(child);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steerable/agent-shell",
3
- "version": "0.6.59",
3
+ "version": "0.6.61",
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/",
@@ -51,9 +51,9 @@
51
51
  "dotenv": "^17.4.2",
52
52
  "he": "^1.2.0",
53
53
  "node-pty": "^1.1.0",
54
- "@steerable/agent-harness": "0.6.59",
55
- "@steerable/agent-protocol": "0.6.59",
56
- "@steerable/pack-sdk": "0.6.59"
54
+ "@steerable/agent-harness": "0.6.61",
55
+ "@steerable/agent-protocol": "0.6.61",
56
+ "@steerable/pack-sdk": "0.6.61"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@types/better-sqlite3": "^7.6.13",