@steerable/agent-shell 0.6.61 → 0.6.63
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/goal-store.d.ts +26 -0
- package/dist/goal-store.js +67 -3
- package/dist/host/runtime.js +4 -0
- package/dist/image-attachment.js +40 -9
- package/dist/local-backend/goal-continuation-helper.d.ts +16 -0
- package/dist/local-backend/goal-continuation-helper.js +36 -0
- package/dist/local-backend/loop-pty-monitor.d.ts +51 -0
- package/dist/local-backend/loop-pty-monitor.js +136 -0
- package/dist/local-backend/router.d.ts +22 -0
- package/dist/local-backend/router.js +203 -7
- package/dist/local-backend/skills/10-goal/SKILL.md +20 -40
- package/dist/local-backend/skills/11-loop/SKILL.md +18 -55
- package/dist/local-backend/skills/85-local-exec/SKILL.md +1 -1
- package/dist/local-backend/skills/86-proactive-coding/SKILL.md +1 -1
- package/dist/local-backend/turn-file-selection.d.ts +21 -0
- package/dist/local-backend/turn-file-selection.js +186 -0
- package/dist/local-backend/turn-files.d.ts +17 -3
- package/dist/local-backend/turn-files.js +239 -28
- package/dist/present-files.d.ts +7 -0
- package/dist/present-files.js +18 -3
- package/dist/raster-image.d.ts +8 -0
- package/dist/raster-image.js +152 -0
- package/dist/tool-router.d.ts +10 -1
- package/dist/tool-router.js +111 -9
- package/package.json +6 -4
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ImageDecoder } from './image-attachment.js';
|
|
2
|
+
/** True when the built-in decoder can read this path's extension. */
|
|
3
|
+
export declare function builtinRasterSupports(filePath: string): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Decode a local PNG or JPEG into a crop/resize/encode surface.
|
|
6
|
+
* Unreadable or unsupported files yield an empty image.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createRasterImageDecoder(): ImageDecoder;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PNG/JPEG decode, crop, resize, and re-encode for the Node host.
|
|
3
|
+
*
|
|
4
|
+
* `view_image` used to require an injected {@link ImageDecoder} (Electron
|
|
5
|
+
* `nativeImage`). The headless host has none, so any image larger than
|
|
6
|
+
* `maxEdge` failed with "当前环境不能缩放". This decoder is the default.
|
|
7
|
+
*/
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import { readFileSync } from 'fs';
|
|
10
|
+
import { extname } from 'path';
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const { PNG } = require('pngjs');
|
|
13
|
+
const jpeg = require('jpeg-js');
|
|
14
|
+
const RASTER_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg']);
|
|
15
|
+
/** True when the built-in decoder can read this path's extension. */
|
|
16
|
+
export function builtinRasterSupports(filePath) {
|
|
17
|
+
return RASTER_EXTENSIONS.has(extname(filePath).toLowerCase());
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Decode a local PNG or JPEG into a crop/resize/encode surface.
|
|
21
|
+
* Unreadable or unsupported files yield an empty image.
|
|
22
|
+
*/
|
|
23
|
+
export function createRasterImageDecoder() {
|
|
24
|
+
return {
|
|
25
|
+
createFromPath(filePath) {
|
|
26
|
+
const decoded = decodeRasterFile(filePath);
|
|
27
|
+
if (!decoded)
|
|
28
|
+
return EMPTY_IMAGE;
|
|
29
|
+
return new RasterImage(decoded.width, decoded.height, decoded.rgba);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function decodeRasterFile(filePath) {
|
|
34
|
+
if (!builtinRasterSupports(filePath))
|
|
35
|
+
return null;
|
|
36
|
+
let bytes;
|
|
37
|
+
try {
|
|
38
|
+
bytes = readFileSync(filePath);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const ext = extname(filePath).toLowerCase();
|
|
45
|
+
if (ext === '.png') {
|
|
46
|
+
const png = PNG.sync.read(bytes);
|
|
47
|
+
return { width: png.width, height: png.height, rgba: Buffer.from(png.data) };
|
|
48
|
+
}
|
|
49
|
+
const decoded = jpeg.decode(bytes, { useTArray: true, formatAsRGBA: true });
|
|
50
|
+
if (decoded.width < 1 || decoded.height < 1)
|
|
51
|
+
return null;
|
|
52
|
+
return {
|
|
53
|
+
width: decoded.width,
|
|
54
|
+
height: decoded.height,
|
|
55
|
+
rgba: Buffer.from(decoded.data),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
class RasterImage {
|
|
63
|
+
width;
|
|
64
|
+
height;
|
|
65
|
+
rgba;
|
|
66
|
+
constructor(width, height, rgba) {
|
|
67
|
+
this.width = width;
|
|
68
|
+
this.height = height;
|
|
69
|
+
this.rgba = rgba;
|
|
70
|
+
}
|
|
71
|
+
isEmpty() {
|
|
72
|
+
return this.width < 1 || this.height < 1 || this.rgba.length < this.width * this.height * 4;
|
|
73
|
+
}
|
|
74
|
+
getSize() {
|
|
75
|
+
return { width: this.width, height: this.height };
|
|
76
|
+
}
|
|
77
|
+
crop(rect) {
|
|
78
|
+
return new RasterImage(rect.width, rect.height, cropRgba(this.rgba, this.width, rect.x, rect.y, rect.width, rect.height));
|
|
79
|
+
}
|
|
80
|
+
resize(options) {
|
|
81
|
+
const width = options.width ?? this.width;
|
|
82
|
+
const height = options.height ?? this.height;
|
|
83
|
+
return new RasterImage(width, height, resizeRgba(this.rgba, this.width, this.height, width, height));
|
|
84
|
+
}
|
|
85
|
+
toPNG() {
|
|
86
|
+
const png = new PNG({ width: this.width, height: this.height });
|
|
87
|
+
this.rgba.copy(png.data);
|
|
88
|
+
return PNG.sync.write(png);
|
|
89
|
+
}
|
|
90
|
+
toJPEG(quality) {
|
|
91
|
+
const encoded = jpeg.encode({ data: this.rgba, width: this.width, height: this.height }, quality);
|
|
92
|
+
return Buffer.from(encoded.data);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const EMPTY_IMAGE = {
|
|
96
|
+
isEmpty: () => true,
|
|
97
|
+
getSize: () => ({ width: 0, height: 0 }),
|
|
98
|
+
crop: () => EMPTY_IMAGE,
|
|
99
|
+
resize: () => EMPTY_IMAGE,
|
|
100
|
+
toPNG: () => Buffer.alloc(0),
|
|
101
|
+
toJPEG: () => Buffer.alloc(0),
|
|
102
|
+
};
|
|
103
|
+
function cropRgba(src, srcWidth, x, y, width, height) {
|
|
104
|
+
const dst = Buffer.alloc(width * height * 4);
|
|
105
|
+
for (let row = 0; row < height; row++) {
|
|
106
|
+
const srcStart = ((y + row) * srcWidth + x) * 4;
|
|
107
|
+
src.copy(dst, row * width * 4, srcStart, srcStart + width * 4);
|
|
108
|
+
}
|
|
109
|
+
return dst;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Area-average downscale. Each output pixel is the mean of the source
|
|
113
|
+
* rectangle it covers, so a long screenshot can be sent under `maxEdge`
|
|
114
|
+
* without aliasing into a single row of samples.
|
|
115
|
+
*/
|
|
116
|
+
function resizeRgba(src, srcWidth, srcHeight, dstWidth, dstHeight) {
|
|
117
|
+
if (dstWidth === srcWidth && dstHeight === srcHeight)
|
|
118
|
+
return Buffer.from(src);
|
|
119
|
+
const dst = Buffer.alloc(Math.max(0, dstWidth * dstHeight * 4));
|
|
120
|
+
if (dstWidth < 1 || dstHeight < 1)
|
|
121
|
+
return dst;
|
|
122
|
+
for (let y = 0; y < dstHeight; y++) {
|
|
123
|
+
const y0 = Math.floor((y * srcHeight) / dstHeight);
|
|
124
|
+
const y1 = Math.min(srcHeight, Math.max(y0 + 1, Math.ceil(((y + 1) * srcHeight) / dstHeight)));
|
|
125
|
+
for (let x = 0; x < dstWidth; x++) {
|
|
126
|
+
const x0 = Math.floor((x * srcWidth) / dstWidth);
|
|
127
|
+
const x1 = Math.min(srcWidth, Math.max(x0 + 1, Math.ceil(((x + 1) * srcWidth) / dstWidth)));
|
|
128
|
+
let r = 0;
|
|
129
|
+
let g = 0;
|
|
130
|
+
let b = 0;
|
|
131
|
+
let a = 0;
|
|
132
|
+
let count = 0;
|
|
133
|
+
for (let sy = y0; sy < y1; sy++) {
|
|
134
|
+
const row = sy * srcWidth;
|
|
135
|
+
for (let sx = x0; sx < x1; sx++) {
|
|
136
|
+
const i = (row + sx) * 4;
|
|
137
|
+
r += src[i];
|
|
138
|
+
g += src[i + 1];
|
|
139
|
+
b += src[i + 2];
|
|
140
|
+
a += src[i + 3];
|
|
141
|
+
count += 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const o = (y * dstWidth + x) * 4;
|
|
145
|
+
dst[o] = Math.round(r / count);
|
|
146
|
+
dst[o + 1] = Math.round(g / count);
|
|
147
|
+
dst[o + 2] = Math.round(b / count);
|
|
148
|
+
dst[o + 3] = Math.round(a / count);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return dst;
|
|
152
|
+
}
|
package/dist/tool-router.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { ToolContribution, ToolMode, ToolExposure } from './scenario/pack.j
|
|
|
8
8
|
export type { ToolMode, ToolExposure } from './scenario/pack.js';
|
|
9
9
|
import { type AgentToolPolicy } from './local-backend/agent-capability.js';
|
|
10
10
|
import { GoalStore } from './goal-store.js';
|
|
11
|
+
import type { LoopPtyMonitor } from './local-backend/loop-pty-monitor.js';
|
|
11
12
|
/** 已注册 MCP 服务的动态工具名前缀:`mcp__<serverKey>__<toolName>`。 */
|
|
12
13
|
export declare const MCP_DYNAMIC_TOOL_PREFIX = "mcp__";
|
|
13
14
|
/**
|
|
@@ -144,14 +145,19 @@ export declare class ToolRouter {
|
|
|
144
145
|
*/
|
|
145
146
|
private taskService;
|
|
146
147
|
private worktreeService;
|
|
148
|
+
private loopMonitor;
|
|
147
149
|
setTaskServices(services: {
|
|
148
150
|
taskService: TaskService;
|
|
149
151
|
worktreeService: WorktreeService;
|
|
150
152
|
} | null): void;
|
|
153
|
+
setLoopMonitor(monitor: Pick<LoopPtyMonitor, 'start' | 'list' | 'stop'> | null): void;
|
|
154
|
+
listMonitoredLoops(chatId: string): import("./local-backend/loop-pty-monitor.js").MonitoredLoop[];
|
|
155
|
+
stopMonitoredLoop(chatId: string, loopId: string): boolean;
|
|
151
156
|
/** 会话目标的持久化。缺省写到用户数据目录;测试注入临时文件。 */
|
|
152
157
|
private goalStore;
|
|
153
158
|
setGoalStore(store: GoalStore | null): void;
|
|
154
|
-
|
|
159
|
+
/** 目标工具使用的存储;LocalBackendRouter 的续跑与目标路由共用同一实例。 */
|
|
160
|
+
goals(): GoalStore;
|
|
155
161
|
setWebTools(delegate: WebToolDelegate | null, names?: readonly string[]): void;
|
|
156
162
|
setHostedWebSearch(handler: ((args: Record<string, unknown>) => Promise<unknown>) | null): void;
|
|
157
163
|
/**
|
|
@@ -189,6 +195,7 @@ export declare class ToolRouter {
|
|
|
189
195
|
* git 仓库。description 用英文(面向模型),与 local_* 工具同款。
|
|
190
196
|
*/
|
|
191
197
|
private listTaskAndWorktreeSchemas;
|
|
198
|
+
private listLoopToolSchemas;
|
|
192
199
|
/**
|
|
193
200
|
* 网络读取对(W5-2):schema 与 sidecar `web_tools.py` 保持一致;description
|
|
194
201
|
* 用英文(面向模型),与 local_* 工具同款。可用集由握手决定,未握手/未注册
|
|
@@ -220,8 +227,10 @@ export declare class ToolRouter {
|
|
|
220
227
|
* 抛错(reverse-tools 总是透传;缺了就是接线 bug,响亮失败)。
|
|
221
228
|
*/
|
|
222
229
|
private executeGoalTool;
|
|
230
|
+
private executeLoopTool;
|
|
223
231
|
private executeJobTool;
|
|
224
232
|
private requireTaskContext;
|
|
233
|
+
private requireChatContext;
|
|
225
234
|
/**
|
|
226
235
|
* Agent 写出 `…/skills/<name>/SKILL.md` 且该目录还不在已知 skill root 上时,
|
|
227
236
|
* 拷进用户技能目录,Skill 设置 / `/` 菜单不必再手动导入。
|
package/dist/tool-router.js
CHANGED
|
@@ -9,7 +9,7 @@ import { processViewImage } from './image-attachment.js';
|
|
|
9
9
|
import { PRESENT_FILES_SCHEMA, PRESENT_FILES_TOOL_NAME, executePresentFiles, } from './present-files.js';
|
|
10
10
|
import { isHostToolCapabilityEnabled } from './host-tools.js';
|
|
11
11
|
import { getResolvedHostTools } from './host-tools-runtime.js';
|
|
12
|
-
import { GoalStore,
|
|
12
|
+
import { GoalStore, MODEL_GOAL_ACTIONS } from './goal-store.js';
|
|
13
13
|
import { searchGlob, searchGrep } from './workspace-search.js';
|
|
14
14
|
/** 已注册 MCP 服务的动态工具名前缀:`mcp__<serverKey>__<toolName>`。 */
|
|
15
15
|
export const MCP_DYNAMIC_TOOL_PREFIX = 'mcp__';
|
|
@@ -95,15 +95,28 @@ export class ToolRouter {
|
|
|
95
95
|
*/
|
|
96
96
|
taskService = null;
|
|
97
97
|
worktreeService = null;
|
|
98
|
+
loopMonitor = null;
|
|
98
99
|
setTaskServices(services) {
|
|
99
100
|
this.taskService = services?.taskService ?? null;
|
|
100
101
|
this.worktreeService = services?.worktreeService ?? null;
|
|
101
102
|
}
|
|
103
|
+
setLoopMonitor(monitor) {
|
|
104
|
+
this.loopMonitor = monitor;
|
|
105
|
+
}
|
|
106
|
+
listMonitoredLoops(chatId) {
|
|
107
|
+
return this.loopMonitor?.list(chatId) ?? [];
|
|
108
|
+
}
|
|
109
|
+
stopMonitoredLoop(chatId, loopId) {
|
|
110
|
+
if (!this.loopMonitor?.list(chatId).some((loop) => loop.id === loopId))
|
|
111
|
+
return false;
|
|
112
|
+
return this.loopMonitor.stop(loopId);
|
|
113
|
+
}
|
|
102
114
|
/** 会话目标的持久化。缺省写到用户数据目录;测试注入临时文件。 */
|
|
103
115
|
goalStore = null;
|
|
104
116
|
setGoalStore(store) {
|
|
105
117
|
this.goalStore = store;
|
|
106
118
|
}
|
|
119
|
+
/** 目标工具使用的存储;LocalBackendRouter 的续跑与目标路由共用同一实例。 */
|
|
107
120
|
goals() {
|
|
108
121
|
if (!this.goalStore)
|
|
109
122
|
this.goalStore = GoalStore.default();
|
|
@@ -433,6 +446,7 @@ export class ToolRouter {
|
|
|
433
446
|
},
|
|
434
447
|
},
|
|
435
448
|
...this.listParityToolSchemas(),
|
|
449
|
+
...this.listLoopToolSchemas(),
|
|
436
450
|
// ─── 4.6a/4.6b 跨 turn 后台任务 + git worktree 隔离 ─────────────
|
|
437
451
|
// 服务未接线(CLI/test)时一个 schema 都不出——模型看不到不可用的工具。
|
|
438
452
|
...this.listTaskAndWorktreeSchemas(),
|
|
@@ -566,8 +580,9 @@ export class ToolRouter {
|
|
|
566
580
|
},
|
|
567
581
|
{
|
|
568
582
|
name: 'create_goal',
|
|
569
|
-
description: 'Create a persisted goal for this chat when the user
|
|
570
|
-
+ '
|
|
583
|
+
description: 'Create a persisted goal for this chat. Only when the user explicitly asks to track a goal; never infer one from an ordinary task. '
|
|
584
|
+
+ 'While the goal is active the host keeps starting follow-up turns until you mark it complete or blocked. '
|
|
585
|
+
+ 'Fails if an unfinished goal already exists.',
|
|
571
586
|
mode: 'safe_write',
|
|
572
587
|
inputSchema: {
|
|
573
588
|
type: 'object',
|
|
@@ -580,17 +595,17 @@ export class ToolRouter {
|
|
|
580
595
|
},
|
|
581
596
|
{
|
|
582
597
|
name: 'update_goal',
|
|
583
|
-
description: 'Change the current goal. Pass the id and revision from get_goal. '
|
|
584
|
-
+ '
|
|
585
|
-
+ '
|
|
598
|
+
description: 'Change the current goal\'s status. Pass the id and revision from get_goal; a stale revision is rejected, re-read and retry. '
|
|
599
|
+
+ 'complete: only after checking every requirement of the objective against current evidence (files, command output, test results) and no required work remains. '
|
|
600
|
+
+ 'blocked (needs reason): only after the same blocker has recurred for at least three consecutive goal turns and no safe next action exists; never because the work is hard or slow. '
|
|
601
|
+
+ 'pause: only when the user explicitly asks. Resuming and editing the objective are user-only.',
|
|
586
602
|
mode: 'safe_write',
|
|
587
603
|
inputSchema: {
|
|
588
604
|
type: 'object',
|
|
589
605
|
properties: {
|
|
590
606
|
id: { type: 'string' },
|
|
591
607
|
revision: { type: 'integer' },
|
|
592
|
-
action: { type: 'string', enum: [...
|
|
593
|
-
objective: { type: 'string', description: 'Required for action=edit.' },
|
|
608
|
+
action: { type: 'string', enum: [...MODEL_GOAL_ACTIONS] },
|
|
594
609
|
reason: { type: 'string', description: 'Required for action=blocked.' },
|
|
595
610
|
},
|
|
596
611
|
required: ['id', 'revision', 'action'],
|
|
@@ -767,6 +782,51 @@ export class ToolRouter {
|
|
|
767
782
|
},
|
|
768
783
|
];
|
|
769
784
|
}
|
|
785
|
+
listLoopToolSchemas() {
|
|
786
|
+
if (!this.loopMonitor)
|
|
787
|
+
return [];
|
|
788
|
+
return [
|
|
789
|
+
{
|
|
790
|
+
name: 'loop_create',
|
|
791
|
+
description: 'Start recurring local work for this chat after running it once in the current turn. '
|
|
792
|
+
+ 'Each interval wakes this chat until the loop is stopped or the host exits.',
|
|
793
|
+
mode: 'safe_write',
|
|
794
|
+
inputSchema: {
|
|
795
|
+
type: 'object',
|
|
796
|
+
properties: {
|
|
797
|
+
prompt: {
|
|
798
|
+
type: 'string',
|
|
799
|
+
description: 'Self-contained work to run on every wake. Include the terminal condition for finite monitoring; '
|
|
800
|
+
+ 'omit a terminal condition for open-ended recurring work.',
|
|
801
|
+
},
|
|
802
|
+
intervalSeconds: {
|
|
803
|
+
type: 'integer',
|
|
804
|
+
description: 'Positive fixed interval in seconds.',
|
|
805
|
+
},
|
|
806
|
+
},
|
|
807
|
+
required: ['prompt', 'intervalSeconds'],
|
|
808
|
+
additionalProperties: false,
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
name: 'loop_list',
|
|
813
|
+
description: 'List recurring local loops owned by this chat.',
|
|
814
|
+
mode: 'read',
|
|
815
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
name: 'loop_stop',
|
|
819
|
+
description: 'Stop one recurring local loop owned by this chat.',
|
|
820
|
+
mode: 'safe_write',
|
|
821
|
+
inputSchema: {
|
|
822
|
+
type: 'object',
|
|
823
|
+
properties: { id: { type: 'string' } },
|
|
824
|
+
required: ['id'],
|
|
825
|
+
additionalProperties: false,
|
|
826
|
+
},
|
|
827
|
+
},
|
|
828
|
+
];
|
|
829
|
+
}
|
|
770
830
|
/**
|
|
771
831
|
* 网络读取对(W5-2):schema 与 sidecar `web_tools.py` 保持一致;description
|
|
772
832
|
* 用英文(面向模型),与 local_* 工具同款。可用集由握手决定,未握手/未注册
|
|
@@ -1054,6 +1114,10 @@ export class ToolRouter {
|
|
|
1054
1114
|
case 'create_goal':
|
|
1055
1115
|
case 'update_goal':
|
|
1056
1116
|
return this.executeGoalTool(call.name, args, context);
|
|
1117
|
+
case 'loop_create':
|
|
1118
|
+
case 'loop_list':
|
|
1119
|
+
case 'loop_stop':
|
|
1120
|
+
return this.executeLoopTool(call.name, args, context);
|
|
1057
1121
|
case 'job_list':
|
|
1058
1122
|
case 'job_output':
|
|
1059
1123
|
case 'job_kill':
|
|
@@ -1141,10 +1205,42 @@ export class ToolRouter {
|
|
|
1141
1205
|
id: String(args.id || ''),
|
|
1142
1206
|
revision: typeof args.revision === 'number' ? args.revision : Number.NaN,
|
|
1143
1207
|
action: String(args.action || ''),
|
|
1144
|
-
|
|
1208
|
+
actor: 'model',
|
|
1145
1209
|
reason: typeof args.reason === 'string' ? args.reason : undefined,
|
|
1146
1210
|
});
|
|
1147
1211
|
}
|
|
1212
|
+
executeLoopTool(name, args, context) {
|
|
1213
|
+
const chatId = this.requireChatContext(context, 'loop tools');
|
|
1214
|
+
if (!this.loopMonitor) {
|
|
1215
|
+
return { success: false, error: 'loop monitor unavailable', needsFollowup: true };
|
|
1216
|
+
}
|
|
1217
|
+
if (name === 'loop_list') {
|
|
1218
|
+
return { success: true, loops: this.loopMonitor.list(chatId) };
|
|
1219
|
+
}
|
|
1220
|
+
if (name === 'loop_stop') {
|
|
1221
|
+
const id = String(args.id || '');
|
|
1222
|
+
const owned = this.loopMonitor.list(chatId).some((loop) => loop.id === id);
|
|
1223
|
+
if (!owned)
|
|
1224
|
+
return { success: false, error: 'loop not found', needsFollowup: true };
|
|
1225
|
+
return { success: this.loopMonitor.stop(id) };
|
|
1226
|
+
}
|
|
1227
|
+
try {
|
|
1228
|
+
const loop = this.loopMonitor.start({
|
|
1229
|
+
chatId,
|
|
1230
|
+
prompt: String(args.prompt || ''),
|
|
1231
|
+
intervalSeconds: typeof args.intervalSeconds === 'number' ? args.intervalSeconds : Number.NaN,
|
|
1232
|
+
cwd: context?.projectRoot ?? undefined,
|
|
1233
|
+
});
|
|
1234
|
+
return { success: true, loop };
|
|
1235
|
+
}
|
|
1236
|
+
catch (error) {
|
|
1237
|
+
return {
|
|
1238
|
+
success: false,
|
|
1239
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1240
|
+
needsFollowup: true,
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1148
1244
|
async executeJobTool(name, args, context) {
|
|
1149
1245
|
const chatId = this.requireTaskContext(context);
|
|
1150
1246
|
const jobId = String(args.job_id || '');
|
|
@@ -1176,6 +1272,12 @@ export class ToolRouter {
|
|
|
1176
1272
|
}
|
|
1177
1273
|
return chatId;
|
|
1178
1274
|
}
|
|
1275
|
+
requireChatContext(context, owner) {
|
|
1276
|
+
const chatId = context?.chatId;
|
|
1277
|
+
if (!chatId)
|
|
1278
|
+
throw new Error(`${owner} 需要 chatId 调用上下文`);
|
|
1279
|
+
return chatId;
|
|
1280
|
+
}
|
|
1179
1281
|
/**
|
|
1180
1282
|
* Agent 写出 `…/skills/<name>/SKILL.md` 且该目录还不在已知 skill root 上时,
|
|
1181
1283
|
* 拷进用户技能目录,Skill 设置 / `/` 菜单不必再手动导入。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-shell",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.63",
|
|
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/",
|
|
@@ -50,10 +50,12 @@
|
|
|
50
50
|
"conf": "10.2.0",
|
|
51
51
|
"dotenv": "^17.4.2",
|
|
52
52
|
"he": "^1.2.0",
|
|
53
|
+
"jpeg-js": "^0.4.4",
|
|
53
54
|
"node-pty": "^1.1.0",
|
|
54
|
-
"
|
|
55
|
-
"@steerable/agent-
|
|
56
|
-
"@steerable/
|
|
55
|
+
"pngjs": "^7.0.0",
|
|
56
|
+
"@steerable/agent-harness": "0.6.63",
|
|
57
|
+
"@steerable/agent-protocol": "0.6.63",
|
|
58
|
+
"@steerable/pack-sdk": "0.6.63"
|
|
57
59
|
},
|
|
58
60
|
"devDependencies": {
|
|
59
61
|
"@types/better-sqlite3": "^7.6.13",
|