@steerable/agent-shell 0.6.60 → 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
|
+
}
|
|
@@ -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 {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-shell",
|
|
3
|
-
"version": "0.6.
|
|
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.
|
|
55
|
-
"@steerable/agent-protocol": "0.6.
|
|
56
|
-
"@steerable/pack-sdk": "0.6.
|
|
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",
|