@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.
- package/dist/local-backend/history-edit.d.ts +27 -0
- package/dist/local-backend/history-edit.js +46 -0
- package/dist/local-backend/router.d.ts +1 -0
- package/dist/local-backend/router.js +49 -0
- package/dist/sidecar/egress-proxy.js +2 -0
- package/dist/sidecar/supervisor.js +1 -0
- package/package.json +4 -4
|
@@ -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 {};
|
|
@@ -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.
|
|
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",
|