dsh-connect 0.1.0 → 0.5.0
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/LICENSE +21 -0
- package/README.md +21 -0
- package/lib/commands.d.ts +20 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +45 -18
- package/lib/commands.js.map +1 -1
- package/lib/i18n.d.ts +158 -0
- package/lib/i18n.d.ts.map +1 -0
- package/lib/i18n.js +309 -0
- package/lib/i18n.js.map +1 -0
- package/lib/index.d.ts +24 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +6 -1
- package/lib/index.js.map +1 -1
- package/lib/runner.d.ts +39 -0
- package/lib/runner.d.ts.map +1 -1
- package/lib/runner.js +605 -100
- package/lib/runner.js.map +1 -1
- package/lib/types.d.ts +27 -3
- package/lib/types.d.ts.map +1 -1
- package/package.json +10 -1
package/lib/runner.js
CHANGED
|
@@ -5,32 +5,27 @@
|
|
|
5
5
|
* @module dsh-connect/runner
|
|
6
6
|
*/
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
|
-
import { existsSync, statSync } from "node:fs";
|
|
9
|
-
import {
|
|
8
|
+
import { existsSync, mkdirSync, statSync } from "node:fs";
|
|
9
|
+
import { copyFile, readFile } from "node:fs/promises";
|
|
10
|
+
import { basename, isAbsolute, join } from "node:path";
|
|
10
11
|
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
11
12
|
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
12
13
|
import { createUserMessage, ReasoningEffortId } from "@deepseek-ai/dsh-llm";
|
|
13
14
|
import { createAsyncQueue } from "./stream.js";
|
|
14
|
-
import {
|
|
15
|
+
import { helpText, parseCommand } from "./commands.js";
|
|
16
|
+
import { messages } from "./i18n.js";
|
|
15
17
|
export function resolveConnectConfig(config) {
|
|
16
18
|
return {
|
|
17
19
|
agentPreset: config.agentPreset,
|
|
18
20
|
workDir: config.workDir,
|
|
19
21
|
workspaces: config.workspaces ?? [],
|
|
22
|
+
visionModel: config.visionModel,
|
|
23
|
+
language: config.language ?? "zh",
|
|
20
24
|
allowUsers: config.allowUsers ?? [],
|
|
21
25
|
allowChats: config.allowChats ?? [],
|
|
22
26
|
stateDir: config.stateDir,
|
|
23
27
|
};
|
|
24
28
|
}
|
|
25
|
-
const REASON_LABELS = {
|
|
26
|
-
completed: "✅ 完成",
|
|
27
|
-
aborted: "⏹️ 已中止",
|
|
28
|
-
blocked: "🚫 阻塞",
|
|
29
|
-
error: "❌ 出错",
|
|
30
|
-
"max-tokens": "⚠️ 达到输出上限",
|
|
31
|
-
interrupted: "⚠️ 被中断",
|
|
32
|
-
unknown: "❓ 未知",
|
|
33
|
-
};
|
|
34
29
|
function mapReason(kind) {
|
|
35
30
|
switch (kind) {
|
|
36
31
|
case "completed":
|
|
@@ -80,15 +75,64 @@ export function summarizeTurn(events, firstSeq) {
|
|
|
80
75
|
function truncate(text, max = 500) {
|
|
81
76
|
return text.length <= max ? text : `${text.slice(0, max)}…`;
|
|
82
77
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
78
|
+
function textOf(message) {
|
|
79
|
+
if (message === undefined)
|
|
80
|
+
return "";
|
|
81
|
+
return message.content
|
|
82
|
+
.filter((b) => b.type === "text" && typeof b.text === "string")
|
|
83
|
+
.map((b) => b.text ?? "")
|
|
84
|
+
.join("");
|
|
85
|
+
}
|
|
86
|
+
/** Lightweight fold of `schedule/change` events (create / delete / dispatch). */
|
|
87
|
+
function foldReminders(events) {
|
|
88
|
+
const active = new Map();
|
|
89
|
+
for (const e of events) {
|
|
90
|
+
// `schedule/change` is a plugin-extended event type outside dsh-session's map.
|
|
91
|
+
const evt = e;
|
|
92
|
+
if (evt.type !== "schedule/change")
|
|
93
|
+
continue;
|
|
94
|
+
const d = evt.data;
|
|
95
|
+
if (d.version !== 1)
|
|
96
|
+
continue;
|
|
97
|
+
if (d.operation === "create" && d.schedule !== undefined) {
|
|
98
|
+
active.set(d.schedule.id, {
|
|
99
|
+
id: d.schedule.id,
|
|
100
|
+
kind: d.schedule.kind,
|
|
101
|
+
prompt: d.schedule.prompt,
|
|
102
|
+
scheduledAt: d.schedule.scheduledAt,
|
|
103
|
+
everySeconds: d.schedule.everySeconds,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else if (d.operation === "delete" && d.id !== undefined) {
|
|
107
|
+
active.delete(d.id);
|
|
108
|
+
}
|
|
109
|
+
else if (d.operation === "dispatch" && d.id !== undefined) {
|
|
110
|
+
const rec = active.get(d.id);
|
|
111
|
+
if (rec === undefined)
|
|
112
|
+
continue;
|
|
113
|
+
if (rec.kind === "every" && rec.everySeconds !== undefined && d.acceptedAt !== undefined) {
|
|
114
|
+
const next = Date.parse(d.acceptedAt) + rec.everySeconds * 1000;
|
|
115
|
+
active.set(d.id, { ...rec, scheduledAt: new Date(next).toISOString() });
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
active.delete(d.id);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return [...active.values()];
|
|
123
|
+
}
|
|
124
|
+
/** Sniff an image's media type from its magic bytes (defaults to PNG). */
|
|
125
|
+
function detectImageMediaType(buf) {
|
|
126
|
+
if (buf.length >= 8 && buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47)
|
|
127
|
+
return "image/png";
|
|
128
|
+
if (buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff)
|
|
129
|
+
return "image/jpeg";
|
|
130
|
+
if (buf.length >= 12 && buf.toString("ascii", 0, 4) === "RIFF" && buf.toString("ascii", 8, 12) === "WEBP")
|
|
131
|
+
return "image/webp";
|
|
132
|
+
if (buf.length >= 6 && buf.toString("ascii", 0, 3) === "GIF")
|
|
133
|
+
return "image/gif";
|
|
134
|
+
return "image/png";
|
|
135
|
+
}
|
|
92
136
|
export class AgentRunner {
|
|
93
137
|
ctx;
|
|
94
138
|
config;
|
|
@@ -105,6 +149,7 @@ export class AgentRunner {
|
|
|
105
149
|
workDir;
|
|
106
150
|
streamEnabled = true;
|
|
107
151
|
summaryEnabled = true;
|
|
152
|
+
t;
|
|
108
153
|
constructor(ctx, config, channel, chatKey, chatType, adapter, bindings) {
|
|
109
154
|
this.ctx = ctx;
|
|
110
155
|
this.config = config;
|
|
@@ -114,6 +159,51 @@ export class AgentRunner {
|
|
|
114
159
|
this.adapter = adapter;
|
|
115
160
|
this.bindings = bindings;
|
|
116
161
|
this.workDir = config.workDir ?? this.resolveDefaultWorkDir();
|
|
162
|
+
this.t = messages(config.language);
|
|
163
|
+
}
|
|
164
|
+
menuTitle(menuId) {
|
|
165
|
+
switch (menuId) {
|
|
166
|
+
case "root":
|
|
167
|
+
return this.t.menuRoot;
|
|
168
|
+
case "workspace":
|
|
169
|
+
return this.t.menuWorkspace;
|
|
170
|
+
case "chat":
|
|
171
|
+
return this.t.menuChat;
|
|
172
|
+
case "settings":
|
|
173
|
+
return this.t.menuSettings;
|
|
174
|
+
case "model":
|
|
175
|
+
return this.t.menuModel;
|
|
176
|
+
case "reasoning":
|
|
177
|
+
return this.t.menuReasoning;
|
|
178
|
+
case "notify":
|
|
179
|
+
return this.t.menuNotify;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
rootMenuSections() {
|
|
183
|
+
return [
|
|
184
|
+
{ title: this.t.rootSectionWorkspace, ids: ["workspace"] },
|
|
185
|
+
{ title: this.t.rootSectionChat, ids: ["chat", "history"] },
|
|
186
|
+
{ title: this.t.rootSectionTask, ids: ["task", "goals", "schedule"] },
|
|
187
|
+
{ title: this.t.rootSectionSystem, ids: ["status", "plugins", "compact", "settings"] },
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
reasonLabel(reason) {
|
|
191
|
+
switch (reason) {
|
|
192
|
+
case "completed":
|
|
193
|
+
return this.t.reasonCompleted;
|
|
194
|
+
case "aborted":
|
|
195
|
+
return this.t.reasonAborted;
|
|
196
|
+
case "blocked":
|
|
197
|
+
return this.t.reasonBlocked;
|
|
198
|
+
case "error":
|
|
199
|
+
return this.t.reasonError;
|
|
200
|
+
case "max-tokens":
|
|
201
|
+
return this.t.reasonMaxTokens;
|
|
202
|
+
case "interrupted":
|
|
203
|
+
return this.t.reasonInterrupted;
|
|
204
|
+
default:
|
|
205
|
+
return this.t.reasonUnknown;
|
|
206
|
+
}
|
|
117
207
|
}
|
|
118
208
|
/** Prefer the user's first DSH workspace over the process cwd as default. */
|
|
119
209
|
resolveDefaultWorkDir() {
|
|
@@ -147,7 +237,7 @@ export class AgentRunner {
|
|
|
147
237
|
}
|
|
148
238
|
else if (chunk.type === "reasoning-delta" && !turn.reasoning) {
|
|
149
239
|
turn.reasoning = true;
|
|
150
|
-
turn.chunks.push(
|
|
240
|
+
turn.chunks.push(this.t.thinkingHint);
|
|
151
241
|
}
|
|
152
242
|
}
|
|
153
243
|
}
|
|
@@ -186,7 +276,7 @@ export class AgentRunner {
|
|
|
186
276
|
catch (error) {
|
|
187
277
|
const detail = error instanceof Error ? error.message : String(error);
|
|
188
278
|
await this.adapter
|
|
189
|
-
.sendText(this.target(msg),
|
|
279
|
+
.sendText(this.target(msg), this.t.processingFailed(truncate(detail)))
|
|
190
280
|
.catch(() => undefined);
|
|
191
281
|
}
|
|
192
282
|
}
|
|
@@ -270,11 +360,12 @@ export class AgentRunner {
|
|
|
270
360
|
}
|
|
271
361
|
async driveAgent(agent, msg) {
|
|
272
362
|
const firstSeq = agent.session.seq;
|
|
363
|
+
const userMessage = async () => {
|
|
364
|
+
const content = await this.buildUserContent(msg);
|
|
365
|
+
return createUserMessage({ content, source: { kind: "user" } });
|
|
366
|
+
};
|
|
273
367
|
if (!this.streamEnabled) {
|
|
274
|
-
agent.followup(
|
|
275
|
-
content: [{ type: "text", text: msg.text }],
|
|
276
|
-
source: { kind: "user" },
|
|
277
|
-
}));
|
|
368
|
+
agent.followup(await userMessage());
|
|
278
369
|
await agent.whenIdle();
|
|
279
370
|
await this.sessions.flush(agent.session);
|
|
280
371
|
const outcome = summarizeTurn(agent.session.events, firstSeq);
|
|
@@ -286,10 +377,7 @@ export class AgentRunner {
|
|
|
286
377
|
const chunks = createAsyncQueue();
|
|
287
378
|
this.turn = { firstSeq, chunks, lastText: "", reasoning: false };
|
|
288
379
|
const streamPromise = this.adapter.streamText(this.target(msg), chunks);
|
|
289
|
-
agent.followup(
|
|
290
|
-
content: [{ type: "text", text: msg.text }],
|
|
291
|
-
source: { kind: "user" },
|
|
292
|
-
}));
|
|
380
|
+
agent.followup(await userMessage());
|
|
293
381
|
await agent.whenIdle();
|
|
294
382
|
await this.sessions.flush(agent.session);
|
|
295
383
|
chunks.end();
|
|
@@ -299,6 +387,187 @@ export class AgentRunner {
|
|
|
299
387
|
this.turn = undefined;
|
|
300
388
|
return { ...outcome, text };
|
|
301
389
|
}
|
|
390
|
+
/**
|
|
391
|
+
* Build the user content for one inbound message. When the message carries
|
|
392
|
+
* images, either pass them directly to the main model (if it supports
|
|
393
|
+
* vision) or run a vision sub-task with a capable model and inject its
|
|
394
|
+
* description as text — so a text-only main model never stalls on images.
|
|
395
|
+
*/
|
|
396
|
+
async buildUserContent(msg) {
|
|
397
|
+
const content = [{ type: "text", text: msg.text }];
|
|
398
|
+
const rawImages = msg.images;
|
|
399
|
+
if (rawImages === undefined || rawImages.length === 0) {
|
|
400
|
+
if (msg.imageError !== undefined) {
|
|
401
|
+
content.push({ type: "text", text: this.t.imageDownloadFailed(msg.imageError) });
|
|
402
|
+
}
|
|
403
|
+
return content;
|
|
404
|
+
}
|
|
405
|
+
// Copy images into the agent's work directory so its tools can access them.
|
|
406
|
+
const paths = await this.stageImages(rawImages);
|
|
407
|
+
const locationText = paths.map((p) => `- ${p}`).join("\n");
|
|
408
|
+
const mainVision = await this.modelSupportsVision(this.agent?.options.provider, this.agent?.options.model);
|
|
409
|
+
if (mainVision) {
|
|
410
|
+
const imageBlocks = await this.imageBlocks(paths);
|
|
411
|
+
if (imageBlocks.length > 0) {
|
|
412
|
+
this.log(`connect: main model supports vision — passing ${imageBlocks.length} image block(s) directly`);
|
|
413
|
+
return [{ type: "text", text: msg.text }, ...imageBlocks];
|
|
414
|
+
}
|
|
415
|
+
this.log("connect: main model supports vision but image blocks failed to attach — falling back to paths");
|
|
416
|
+
return content;
|
|
417
|
+
}
|
|
418
|
+
const description = await this.describeImages(paths);
|
|
419
|
+
const note = this.t.imagesStaged(locationText, description);
|
|
420
|
+
content.push({ type: "text", text: note });
|
|
421
|
+
return content;
|
|
422
|
+
}
|
|
423
|
+
/** Copy images into `<workDir>/.dsh-connect-images` so the agent's tools can reach them. */
|
|
424
|
+
async stageImages(paths) {
|
|
425
|
+
const dir = join(this.workDir, ".dsh-connect-images");
|
|
426
|
+
try {
|
|
427
|
+
mkdirSync(dir, { recursive: true });
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
return [...paths];
|
|
431
|
+
}
|
|
432
|
+
const staged = [];
|
|
433
|
+
for (const p of paths) {
|
|
434
|
+
const target = join(dir, basename(p));
|
|
435
|
+
try {
|
|
436
|
+
await copyFile(p, target);
|
|
437
|
+
staged.push(target);
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
staged.push(p);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return staged;
|
|
444
|
+
}
|
|
445
|
+
async imageBlocks(paths) {
|
|
446
|
+
const attachments = this.ctx.get("attachments");
|
|
447
|
+
if (attachments?.saveImage === undefined)
|
|
448
|
+
return [];
|
|
449
|
+
const blocks = [];
|
|
450
|
+
for (const path of paths) {
|
|
451
|
+
try {
|
|
452
|
+
const buf = await readFile(path);
|
|
453
|
+
const mediaType = detectImageMediaType(buf);
|
|
454
|
+
const ref = await attachments.saveImage({ data: new Uint8Array(buf), mediaType });
|
|
455
|
+
blocks.push({ type: "image", attachment: ref });
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
// Skip unreadable / unsupported images.
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return blocks;
|
|
462
|
+
}
|
|
463
|
+
async describeImages(paths) {
|
|
464
|
+
const vision = await this.findVisionModel();
|
|
465
|
+
if (vision === null) {
|
|
466
|
+
this.log("connect: describeImages skipped — no vision model available");
|
|
467
|
+
return "";
|
|
468
|
+
}
|
|
469
|
+
const attachments = this.ctx.get("attachments");
|
|
470
|
+
const llm = this.ctx.get("llm");
|
|
471
|
+
if (attachments?.saveImage === undefined || llm?.stream === undefined) {
|
|
472
|
+
this.log(`connect: describeImages skipped — attachments.saveImage=${attachments?.saveImage !== undefined}, llm.stream=${llm?.stream !== undefined}`);
|
|
473
|
+
return "";
|
|
474
|
+
}
|
|
475
|
+
const refs = [];
|
|
476
|
+
for (const path of paths) {
|
|
477
|
+
try {
|
|
478
|
+
const buf = await readFile(path);
|
|
479
|
+
refs.push(await attachments.saveImage({ data: new Uint8Array(buf), mediaType: detectImageMediaType(buf) }));
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
this.log(`connect: saveImage failed for ${path}: ${String(error)}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (refs.length === 0) {
|
|
486
|
+
this.log("connect: describeImages skipped — no image could be attached");
|
|
487
|
+
return "";
|
|
488
|
+
}
|
|
489
|
+
this.log(`connect: describing ${refs.length} image(s) with ${vision.provider}/${vision.model}`);
|
|
490
|
+
const messages = [
|
|
491
|
+
{
|
|
492
|
+
role: "user",
|
|
493
|
+
content: [
|
|
494
|
+
{ type: "text", text: this.t.visionPrompt },
|
|
495
|
+
...refs.map((ref) => ({ type: "image", attachment: ref })),
|
|
496
|
+
],
|
|
497
|
+
},
|
|
498
|
+
];
|
|
499
|
+
try {
|
|
500
|
+
let text = "";
|
|
501
|
+
const stream = llm.stream({ provider: vision.provider, model: vision.model, messages, maxTokens: 2048 });
|
|
502
|
+
for await (const chunk of stream) {
|
|
503
|
+
if (chunk.type === "text-delta" && typeof chunk.text === "string")
|
|
504
|
+
text += chunk.text;
|
|
505
|
+
}
|
|
506
|
+
this.log(`connect: vision description produced ${text.length} char(s)`);
|
|
507
|
+
return text.trim();
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
this.log(`connect: vision description call failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
511
|
+
return "";
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
async modelSupportsVision(provider, model) {
|
|
515
|
+
if (provider === undefined || model === undefined)
|
|
516
|
+
return false;
|
|
517
|
+
const llm = this.ctx.get("llm");
|
|
518
|
+
try {
|
|
519
|
+
const info = await llm?.resolveModelInfo?.(provider, model);
|
|
520
|
+
const supports = info?.inputModalities?.includes("image") ?? false;
|
|
521
|
+
this.log(`connect: main model vision check ${provider}/${model} -> ${supports} (inputModalities=${JSON.stringify(info?.inputModalities)})`);
|
|
522
|
+
return supports;
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
this.log(`connect: main model vision check ${provider}/${model} failed: ${String(error)}`);
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
async findVisionModel() {
|
|
530
|
+
if (this.config.visionModel !== undefined) {
|
|
531
|
+
this.log(`connect: vision model configured: ${this.config.visionModel.provider}/${this.config.visionModel.model}`);
|
|
532
|
+
return this.config.visionModel;
|
|
533
|
+
}
|
|
534
|
+
const llm = this.ctx.get("llm");
|
|
535
|
+
if (llm?.listProviders === undefined || llm.resolveModelInfo === undefined) {
|
|
536
|
+
this.log("connect: vision auto-detection unavailable — llm service lacks listProviders/resolveModelInfo");
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
const providers = llm.listProviders() ?? [];
|
|
540
|
+
this.log(`connect: vision auto-detection scanning ${providers.length} provider(s): ${providers.map((p) => p.id).join(", ") || "(none)"}`);
|
|
541
|
+
for (const p of providers) {
|
|
542
|
+
let models = [];
|
|
543
|
+
try {
|
|
544
|
+
models = (await llm.listModels?.(p.id)) ?? [];
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
this.log(`connect: listModels(${p.id}) failed: ${String(error)}`);
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
this.log(`connect: provider ${p.id} advertises ${models.length} model(s)`);
|
|
551
|
+
for (const m of models) {
|
|
552
|
+
try {
|
|
553
|
+
const info = await llm.resolveModelInfo(p.id, m.id);
|
|
554
|
+
this.log(`connect: ${p.id}/${m.id} inputModalities=${JSON.stringify(info.inputModalities)}`);
|
|
555
|
+
if (info.inputModalities?.includes("image")) {
|
|
556
|
+
this.log(`connect: vision model selected: ${p.id}/${m.id}`);
|
|
557
|
+
return { provider: p.id, model: m.id };
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
this.log(`connect: resolveModelInfo(${p.id}/${m.id}) failed: ${String(error)}`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
this.log("connect: no image-capable model found via auto-detection");
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
log(message) {
|
|
569
|
+
this.ctx.get("logger")?.info?.(message);
|
|
570
|
+
}
|
|
302
571
|
readTodos() {
|
|
303
572
|
const agent = this.agent;
|
|
304
573
|
if (agent === undefined)
|
|
@@ -318,13 +587,13 @@ export class AgentRunner {
|
|
|
318
587
|
this.bindings.put({ ...binding, ownerKey: msg.senderKey, lastActiveAt: Date.now() });
|
|
319
588
|
}
|
|
320
589
|
async sendSummary(msg, outcome) {
|
|
321
|
-
const lines = [
|
|
590
|
+
const lines = [this.t.taskEnded(this.reasonLabel(outcome.reason))];
|
|
322
591
|
if (outcome.code !== undefined)
|
|
323
|
-
lines.push(
|
|
592
|
+
lines.push(this.t.errorCode(outcome.code));
|
|
324
593
|
if (outcome.message !== undefined)
|
|
325
|
-
lines.push(
|
|
594
|
+
lines.push(this.t.reasonMessage(truncate(outcome.message)));
|
|
326
595
|
if (outcome.text !== "")
|
|
327
|
-
lines.push(
|
|
596
|
+
lines.push(this.t.produced(truncate(outcome.text, 300)));
|
|
328
597
|
const card = { markdown: lines.join("\n") };
|
|
329
598
|
await this.adapter.sendCard(this.target(msg), card).catch(() => undefined);
|
|
330
599
|
}
|
|
@@ -333,17 +602,17 @@ export class AgentRunner {
|
|
|
333
602
|
switch (command.kind) {
|
|
334
603
|
case "new": {
|
|
335
604
|
await this.newChat(msg);
|
|
336
|
-
await this.adapter.sendText(target,
|
|
605
|
+
await this.adapter.sendText(target, this.t.newChatDone);
|
|
337
606
|
break;
|
|
338
607
|
}
|
|
339
608
|
case "clear": {
|
|
340
609
|
await this.clearChat(msg);
|
|
341
|
-
await this.adapter.sendText(target,
|
|
610
|
+
await this.adapter.sendText(target, this.t.chatCleared);
|
|
342
611
|
break;
|
|
343
612
|
}
|
|
344
613
|
case "stop": {
|
|
345
614
|
this.agent?.cancel({ kind: "user" });
|
|
346
|
-
await this.adapter.sendText(target,
|
|
615
|
+
await this.adapter.sendText(target, this.t.stopRequested);
|
|
347
616
|
break;
|
|
348
617
|
}
|
|
349
618
|
case "status": {
|
|
@@ -361,22 +630,22 @@ export class AgentRunner {
|
|
|
361
630
|
}
|
|
362
631
|
const path = command.path;
|
|
363
632
|
if (!isAbsolute(path)) {
|
|
364
|
-
await this.adapter.sendText(target,
|
|
633
|
+
await this.adapter.sendText(target, this.t.dirUsage);
|
|
365
634
|
break;
|
|
366
635
|
}
|
|
367
636
|
try {
|
|
368
637
|
if (!existsSync(path) || !statSync(path).isDirectory()) {
|
|
369
|
-
await this.adapter.sendText(target,
|
|
638
|
+
await this.adapter.sendText(target, this.t.dirNotExists(path));
|
|
370
639
|
break;
|
|
371
640
|
}
|
|
372
641
|
}
|
|
373
642
|
catch {
|
|
374
|
-
await this.adapter.sendText(target,
|
|
643
|
+
await this.adapter.sendText(target, this.t.dirUnreadable(path));
|
|
375
644
|
break;
|
|
376
645
|
}
|
|
377
646
|
this.workDir = path;
|
|
378
647
|
await this.newChat(msg);
|
|
379
|
-
await this.adapter.sendText(target,
|
|
648
|
+
await this.adapter.sendText(target, this.t.dirSwitched(path));
|
|
380
649
|
break;
|
|
381
650
|
}
|
|
382
651
|
case "chat": {
|
|
@@ -391,8 +660,44 @@ export class AgentRunner {
|
|
|
391
660
|
await this.openMenu(target, msg, "settings", ["root"]);
|
|
392
661
|
break;
|
|
393
662
|
}
|
|
663
|
+
case "plugins": {
|
|
664
|
+
await this.showPlugins(target);
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
case "workspace": {
|
|
668
|
+
if (command.path === undefined) {
|
|
669
|
+
await this.adapter.sendText(target, this.t.workspaceUsage);
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
await this.createWorkspace(command.path, target);
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
case "compact": {
|
|
676
|
+
await this.compact(target);
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
case "history": {
|
|
680
|
+
await this.showHistory(target, command.limit ?? 10);
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
case "goals": {
|
|
684
|
+
await this.showGoals(target);
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
case "schedule": {
|
|
688
|
+
await this.showSchedule(target);
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
case "model": {
|
|
692
|
+
await this.openMenu(target, msg, "model", ["root"]);
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
case "workspaces": {
|
|
696
|
+
await this.showAllWorkspaces(target);
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
394
699
|
case "help": {
|
|
395
|
-
await this.adapter.sendText(target,
|
|
700
|
+
await this.adapter.sendText(target, helpText(this.t));
|
|
396
701
|
break;
|
|
397
702
|
}
|
|
398
703
|
default:
|
|
@@ -466,50 +771,68 @@ export class AgentRunner {
|
|
|
466
771
|
seen.add(key);
|
|
467
772
|
out.push({ path, title: title || path });
|
|
468
773
|
};
|
|
469
|
-
add(this.workDir,
|
|
774
|
+
add(this.workDir, this.t.currentDir);
|
|
470
775
|
for (const w of registry?.list?.() ?? [])
|
|
471
776
|
add(w.path, w.title);
|
|
472
777
|
for (const p of this.config.workspaces)
|
|
473
778
|
add(p, p);
|
|
474
779
|
return out;
|
|
475
780
|
}
|
|
476
|
-
async openMenu(target, msg, menuId, stack = []) {
|
|
781
|
+
async openMenu(target, msg, menuId, stack = [], cardId) {
|
|
477
782
|
const items = await this.menuItems(menuId);
|
|
478
783
|
const options = items.map((i) => ({ id: i.id, label: i.label }));
|
|
784
|
+
options.push({ id: "menu:exit", label: this.t.menuExit });
|
|
479
785
|
if (stack.length > 0)
|
|
480
|
-
options.push({ id: "menu:back", label:
|
|
481
|
-
const choice = await this.adapter.promptChoice(target, {
|
|
786
|
+
options.push({ id: "menu:back", label: this.t.menuBack });
|
|
787
|
+
const { choice, messageId } = await this.adapter.promptChoice(target, {
|
|
788
|
+
title: this.menuTitle(menuId),
|
|
789
|
+
options,
|
|
790
|
+
...(menuId === "root" ? { sections: this.rootMenuSections(), footer: this.t.rootMenuFooter } : {}),
|
|
791
|
+
}, cardId);
|
|
482
792
|
if (choice === undefined)
|
|
483
793
|
return;
|
|
794
|
+
if (choice === "menu:exit") {
|
|
795
|
+
await this.adapter.closeMenu(messageId, this.t.menuClosed);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
484
798
|
if (choice === "menu:back") {
|
|
485
799
|
const parent = stack[stack.length - 1];
|
|
486
|
-
await this.openMenu(target, msg, parent, stack.slice(0, -1));
|
|
800
|
+
await this.openMenu(target, msg, parent, stack.slice(0, -1), messageId);
|
|
487
801
|
return;
|
|
488
802
|
}
|
|
489
803
|
const item = items.find((i) => i.id === choice);
|
|
490
804
|
if (item === undefined)
|
|
491
805
|
return;
|
|
492
|
-
await item.onSelect(target, msg);
|
|
806
|
+
await item.onSelect(target, msg, messageId);
|
|
807
|
+
if (item.leaf === true) {
|
|
808
|
+
// Return to the root menu on the same card so the user can keep operating.
|
|
809
|
+
await this.openMenu(target, msg, "root", [], messageId);
|
|
810
|
+
}
|
|
493
811
|
}
|
|
494
812
|
async menuItems(menuId) {
|
|
495
813
|
switch (menuId) {
|
|
496
814
|
case "root":
|
|
497
815
|
return [
|
|
498
|
-
{ id: "workspace", label:
|
|
499
|
-
{ id: "chat", label:
|
|
500
|
-
{ id: "status", label:
|
|
501
|
-
{ id: "task", label:
|
|
502
|
-
{ id: "
|
|
816
|
+
{ id: "workspace", label: this.t.menuWorkspaceAction, onSelect: (t, m, cardId) => this.openMenu(t, m, "workspace", ["root"], cardId) },
|
|
817
|
+
{ id: "chat", label: this.t.menuChatAction, onSelect: (t, m, cardId) => this.openMenu(t, m, "chat", ["root"], cardId) },
|
|
818
|
+
{ id: "status", label: this.t.menuStatusAction, leaf: true, onSelect: async (t) => { await this.showStatus(t); } },
|
|
819
|
+
{ id: "task", label: this.t.menuTaskAction, leaf: true, onSelect: async (t) => { await this.showTasks(t); } },
|
|
820
|
+
{ id: "history", label: this.t.menuHistoryAction, leaf: true, onSelect: async (t) => { await this.showHistory(t, 10); } },
|
|
821
|
+
{ id: "goals", label: this.t.menuGoalsAction, leaf: true, onSelect: async (t) => { await this.showGoals(t); } },
|
|
822
|
+
{ id: "schedule", label: this.t.menuScheduleAction, leaf: true, onSelect: async (t) => { await this.showSchedule(t); } },
|
|
823
|
+
{ id: "compact", label: this.t.menuCompactAction, leaf: true, onSelect: async (t) => { await this.compact(t); } },
|
|
824
|
+
{ id: "plugins", label: this.t.menuPluginsAction, leaf: true, onSelect: async (t) => { await this.showPlugins(t); } },
|
|
825
|
+
{ id: "settings", label: this.t.menuSettingsAction, onSelect: (t, m, cardId) => this.openMenu(t, m, "settings", ["root"], cardId) },
|
|
503
826
|
];
|
|
504
827
|
case "workspace": {
|
|
505
828
|
const workspaces = this.listWorkspaces();
|
|
506
829
|
return workspaces.map((w) => ({
|
|
507
830
|
id: `dir:${w.path}`,
|
|
508
831
|
label: `${w.path === this.workDir ? "● " : ""}${w.title}${w.title !== w.path ? ` (${w.path})` : ""}`,
|
|
832
|
+
leaf: true,
|
|
509
833
|
onSelect: async (t, m) => {
|
|
510
834
|
this.workDir = w.path;
|
|
511
835
|
await this.newChat(m);
|
|
512
|
-
await this.adapter.sendText(t, `工作目录已切换为:\n${w.path}`);
|
|
513
836
|
},
|
|
514
837
|
}));
|
|
515
838
|
}
|
|
@@ -520,27 +843,27 @@ export class AgentRunner {
|
|
|
520
843
|
const items = sessions.map((s) => ({
|
|
521
844
|
id: `session:${s.sessionId}`,
|
|
522
845
|
label: `${s.sessionId === active ? "●" : "○"} ${s.title || s.sessionId}`,
|
|
846
|
+
leaf: true,
|
|
523
847
|
onSelect: async (t, m) => {
|
|
524
848
|
await this.switchTo(s.sessionId, m.senderKey);
|
|
525
|
-
await this.adapter.sendText(t, `已切换到对话:${s.title || s.sessionId}`);
|
|
526
849
|
},
|
|
527
850
|
}));
|
|
528
851
|
items.push({
|
|
529
852
|
id: "action:new",
|
|
530
|
-
label:
|
|
853
|
+
label: this.t.menuNewChat,
|
|
854
|
+
leaf: true,
|
|
531
855
|
onSelect: async (t, m) => {
|
|
532
856
|
await this.newChat(m);
|
|
533
|
-
await this.adapter.sendText(t, "已开启新对话。");
|
|
534
857
|
},
|
|
535
858
|
});
|
|
536
859
|
return items;
|
|
537
860
|
}
|
|
538
861
|
case "settings":
|
|
539
862
|
return [
|
|
540
|
-
{ id: "model", label:
|
|
541
|
-
{ id: "reasoning", label:
|
|
542
|
-
{ id: "notify", label:
|
|
543
|
-
{ id: "overview", label:
|
|
863
|
+
{ id: "model", label: this.t.menuSettingsModel, onSelect: (t, m, cardId) => this.openMenu(t, m, "model", ["settings", "root"], cardId) },
|
|
864
|
+
{ id: "reasoning", label: this.t.menuSettingsReasoning, onSelect: (t, m, cardId) => this.openMenu(t, m, "reasoning", ["settings", "root"], cardId) },
|
|
865
|
+
{ id: "notify", label: this.t.menuSettingsNotify, onSelect: (t, m, cardId) => this.openMenu(t, m, "notify", ["settings", "root"], cardId) },
|
|
866
|
+
{ id: "overview", label: this.t.menuSettingsOverview, leaf: true, onSelect: async (t) => { await this.showSettings(t); } },
|
|
544
867
|
];
|
|
545
868
|
case "model":
|
|
546
869
|
return await this.modelMenuItems();
|
|
@@ -550,18 +873,18 @@ export class AgentRunner {
|
|
|
550
873
|
return [
|
|
551
874
|
{
|
|
552
875
|
id: "stream",
|
|
553
|
-
label:
|
|
554
|
-
|
|
876
|
+
label: this.t.streamLabel(this.streamEnabled),
|
|
877
|
+
leaf: true,
|
|
878
|
+
onSelect: async () => {
|
|
555
879
|
this.streamEnabled = !this.streamEnabled;
|
|
556
|
-
await this.adapter.sendText(t, `流式输出已${this.streamEnabled ? "开启" : "关闭"}。`);
|
|
557
880
|
},
|
|
558
881
|
},
|
|
559
882
|
{
|
|
560
883
|
id: "summary",
|
|
561
|
-
label:
|
|
562
|
-
|
|
884
|
+
label: this.t.summaryLabel(this.summaryEnabled),
|
|
885
|
+
leaf: true,
|
|
886
|
+
onSelect: async () => {
|
|
563
887
|
this.summaryEnabled = !this.summaryEnabled;
|
|
564
|
-
await this.adapter.sendText(t, `结束摘要已${this.summaryEnabled ? "开启" : "关闭"}。`);
|
|
565
888
|
},
|
|
566
889
|
},
|
|
567
890
|
];
|
|
@@ -570,30 +893,40 @@ export class AgentRunner {
|
|
|
570
893
|
async showStatus(target) {
|
|
571
894
|
const agent = this.agent;
|
|
572
895
|
if (agent === undefined) {
|
|
573
|
-
await this.adapter.sendText(target,
|
|
896
|
+
await this.adapter.sendText(target, this.t.statusNoSession(this.workDir));
|
|
574
897
|
return;
|
|
575
898
|
}
|
|
576
|
-
const label = agent.status === "running" ?
|
|
899
|
+
const label = agent.status === "running" ? this.t.statusRunning : this.t.statusIdle;
|
|
577
900
|
const model = `${agent.options.provider ?? "-"}/${agent.options.model ?? "-"}`;
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
]
|
|
901
|
+
const lines = [
|
|
902
|
+
this.t.statusField(label),
|
|
903
|
+
this.t.modelField(model),
|
|
904
|
+
this.t.workdirField(this.workDir),
|
|
905
|
+
this.t.queuedField(this.queue.length),
|
|
906
|
+
this.t.sessionField(agent.id),
|
|
907
|
+
];
|
|
908
|
+
const tokenMeter = this.ctx.get("tokenMeter");
|
|
909
|
+
try {
|
|
910
|
+
const m = tokenMeter?.measure?.(agent.session);
|
|
911
|
+
if (m !== undefined)
|
|
912
|
+
lines.push(this.t.contextField(m.totalTokens, m.surfaceTokens));
|
|
913
|
+
}
|
|
914
|
+
catch {
|
|
915
|
+
// Token meter unavailable: skip.
|
|
916
|
+
}
|
|
917
|
+
await this.adapter.sendText(target, lines.join("\n"));
|
|
585
918
|
}
|
|
586
919
|
async showTasks(target) {
|
|
587
920
|
const todos = this.readTodos();
|
|
588
921
|
if (todos.length === 0) {
|
|
589
|
-
await this.adapter.sendText(target,
|
|
922
|
+
await this.adapter.sendText(target, this.t.noTodos);
|
|
590
923
|
return;
|
|
591
924
|
}
|
|
592
925
|
const lines = todos.map((todo) => {
|
|
593
926
|
const mark = todo.status === "completed" ? "✅" : todo.status === "in_progress" ? "🔄" : "⬜";
|
|
594
927
|
return `${mark} ${todo.content}`;
|
|
595
928
|
});
|
|
596
|
-
await this.adapter.sendText(target,
|
|
929
|
+
await this.adapter.sendText(target, this.t.currentTodos(todos.length, lines.join("\n")));
|
|
597
930
|
}
|
|
598
931
|
async showSettings(target) {
|
|
599
932
|
const binding = this.bindings.get(this.channel, this.chatKey);
|
|
@@ -601,21 +934,21 @@ export class AgentRunner {
|
|
|
601
934
|
const model = agent ? `${agent.options.provider ?? "-"}/${agent.options.model ?? "-"}` : "-";
|
|
602
935
|
const sel = this.defaultSelection();
|
|
603
936
|
const lines = [
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
937
|
+
this.t.settingsHeader,
|
|
938
|
+
this.t.modelSetting(model),
|
|
939
|
+
this.t.reasoningSetting(sel.reasoningEffort ?? this.t.effortDefault),
|
|
940
|
+
this.t.agentPresetSetting(this.config.agentPreset ?? this.t.defaultLabel),
|
|
941
|
+
this.t.workdirSetting(this.workDir),
|
|
942
|
+
this.t.workspacesSetting + (this.config.workspaces.length === 0 ? this.t.notConfigured : ""),
|
|
610
943
|
];
|
|
611
944
|
for (const w of this.config.workspaces)
|
|
612
945
|
lines.push(` - ${w}`);
|
|
613
|
-
lines.push(
|
|
614
|
-
lines.push(
|
|
615
|
-
lines.push(
|
|
616
|
-
lines.push(
|
|
617
|
-
lines.push(
|
|
618
|
-
lines.push(
|
|
946
|
+
lines.push(this.t.chatCountField(binding?.sessions.length ?? 0));
|
|
947
|
+
lines.push(this.t.streamLabel(this.streamEnabled));
|
|
948
|
+
lines.push(this.t.summaryLabel(this.summaryEnabled));
|
|
949
|
+
lines.push(this.t.allowUsersField(this.config.allowUsers.length, this.config.allowUsers.length === 0));
|
|
950
|
+
lines.push(this.t.allowChatsField(this.config.allowChats.length, this.config.allowChats.length === 0));
|
|
951
|
+
lines.push(this.t.stateDirField(this.config.stateDir ?? ".dsh-connect"));
|
|
619
952
|
await this.adapter.sendText(target, lines.join("\n"));
|
|
620
953
|
}
|
|
621
954
|
async modelMenuItems() {
|
|
@@ -624,13 +957,13 @@ export class AgentRunner {
|
|
|
624
957
|
const items = choices.map((c) => ({
|
|
625
958
|
id: `model:${c.provider}:${c.model}`,
|
|
626
959
|
label: `${c.provider === current.provider && c.model === current.model ? "● " : ""}${c.name}`,
|
|
960
|
+
leaf: true,
|
|
627
961
|
onSelect: async (t, m) => {
|
|
628
962
|
await this.setModel(c.provider, c.model, m);
|
|
629
|
-
await this.adapter.sendText(t, `模型已切换为:${c.name}`);
|
|
630
963
|
},
|
|
631
964
|
}));
|
|
632
965
|
if (items.length === 0) {
|
|
633
|
-
items.push({ id: "model:none", label:
|
|
966
|
+
items.push({ id: "model:none", label: this.t.noModelsFound, onSelect: async () => { } });
|
|
634
967
|
}
|
|
635
968
|
return items;
|
|
636
969
|
}
|
|
@@ -647,7 +980,7 @@ export class AgentRunner {
|
|
|
647
980
|
// Skip providers whose catalog cannot be listed.
|
|
648
981
|
}
|
|
649
982
|
for (const m of models) {
|
|
650
|
-
out.push({ provider: p.id, model: m.id, name:
|
|
983
|
+
out.push({ provider: p.id, model: m.id, name: this.t.modelName(m.name || m.id, p.name || p.id) });
|
|
651
984
|
}
|
|
652
985
|
}
|
|
653
986
|
return out;
|
|
@@ -665,17 +998,17 @@ export class AgentRunner {
|
|
|
665
998
|
reasoningMenuItems() {
|
|
666
999
|
const current = this.defaultSelection();
|
|
667
1000
|
const efforts = [
|
|
668
|
-
{ id: "low", name:
|
|
669
|
-
{ id: "medium", name:
|
|
670
|
-
{ id: "high", name:
|
|
1001
|
+
{ id: "low", name: this.t.effortLow },
|
|
1002
|
+
{ id: "medium", name: this.t.effortMedium },
|
|
1003
|
+
{ id: "high", name: this.t.effortHigh },
|
|
671
1004
|
];
|
|
672
1005
|
const items = [
|
|
673
1006
|
{
|
|
674
1007
|
id: "effort:default",
|
|
675
|
-
label: `${current.reasoningEffort === undefined ? "● " : ""}
|
|
1008
|
+
label: `${current.reasoningEffort === undefined ? "● " : ""}${this.t.effortDefault}`,
|
|
1009
|
+
leaf: true,
|
|
676
1010
|
onSelect: async (t, m) => {
|
|
677
1011
|
await this.setReasoning(undefined, m);
|
|
678
|
-
await this.adapter.sendText(t, "推理强度已设为默认。");
|
|
679
1012
|
},
|
|
680
1013
|
},
|
|
681
1014
|
];
|
|
@@ -683,9 +1016,9 @@ export class AgentRunner {
|
|
|
683
1016
|
items.push({
|
|
684
1017
|
id: `effort:${e.id}`,
|
|
685
1018
|
label: `${e.id === current.reasoningEffort ? "● " : ""}${e.name}`,
|
|
1019
|
+
leaf: true,
|
|
686
1020
|
onSelect: async (t, m) => {
|
|
687
1021
|
await this.setReasoning(e.id, m);
|
|
688
|
-
await this.adapter.sendText(t, `推理强度已切换为:${e.name}`);
|
|
689
1022
|
},
|
|
690
1023
|
});
|
|
691
1024
|
}
|
|
@@ -701,5 +1034,177 @@ export class AgentRunner {
|
|
|
701
1034
|
});
|
|
702
1035
|
await this.newChat(msg);
|
|
703
1036
|
}
|
|
1037
|
+
async showPlugins(target) {
|
|
1038
|
+
const loader = this.ctx.get("loader");
|
|
1039
|
+
const all = [...(loader?.entries?.() ?? [])];
|
|
1040
|
+
if (all.length === 0) {
|
|
1041
|
+
await this.adapter.sendText(target, this.t.noPlugins);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
const shown = all.slice(0, 50);
|
|
1045
|
+
const lines = shown.map((e) => {
|
|
1046
|
+
const status = e.disabled ? this.t.pluginDisabled : this.t.pluginEnabled;
|
|
1047
|
+
return `${status} ${e.id}${e.options.name ? ` (${e.options.name})` : ""}`;
|
|
1048
|
+
});
|
|
1049
|
+
if (all.length > shown.length)
|
|
1050
|
+
lines.push(this.t.pluginsTruncated(all.length, shown.length));
|
|
1051
|
+
await this.adapter.sendText(target, this.t.pluginsCount(all.length) + lines.join("\n"));
|
|
1052
|
+
}
|
|
1053
|
+
async createWorkspace(path, target) {
|
|
1054
|
+
if (!isAbsolute(path)) {
|
|
1055
|
+
await this.adapter.sendText(target, this.t.workspaceUsage);
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
try {
|
|
1059
|
+
if (!existsSync(path) || !statSync(path).isDirectory()) {
|
|
1060
|
+
await this.adapter.sendText(target, this.t.dirNotExistsHint(path));
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
catch {
|
|
1065
|
+
await this.adapter.sendText(target, this.t.dirUnreadable(path));
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
const registry = this.ctx.get("workspaceRegistry");
|
|
1069
|
+
if (registry?.create === undefined) {
|
|
1070
|
+
await this.adapter.sendText(target, this.t.workspaceServiceUnavailable);
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
try {
|
|
1074
|
+
const ws = await registry.create(path);
|
|
1075
|
+
await this.adapter.sendText(target, this.t.workspaceCreated(ws.title, ws.path));
|
|
1076
|
+
}
|
|
1077
|
+
catch (error) {
|
|
1078
|
+
await this.adapter.sendText(target, this.t.createFailed(error instanceof Error ? error.message : String(error)));
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
async compact(target) {
|
|
1082
|
+
const agent = this.agent;
|
|
1083
|
+
if (agent === undefined) {
|
|
1084
|
+
await this.adapter.sendText(target, this.t.noActiveSessionCompact);
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
if (agent.status !== "idle") {
|
|
1088
|
+
await this.adapter.sendText(target, this.t.sessionRunning);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
const presets = this.ctx.get("agentPresets");
|
|
1092
|
+
const compaction = presets?.serviceFor?.(agent, "compaction");
|
|
1093
|
+
if (compaction?.compactNow === undefined) {
|
|
1094
|
+
await this.adapter.sendText(target, this.t.compactionUnavailable);
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
try {
|
|
1098
|
+
const result = await compaction.compactNow(agent, new AbortController().signal);
|
|
1099
|
+
if (result === null) {
|
|
1100
|
+
await this.adapter.sendText(target, this.t.nothingToCompact);
|
|
1101
|
+
}
|
|
1102
|
+
else {
|
|
1103
|
+
await this.adapter.sendText(target, this.t.contextCompacted);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
catch (error) {
|
|
1107
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1108
|
+
await this.adapter.sendText(target, this.t.compactFailed(truncate(msg)));
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
async showHistory(target, limit) {
|
|
1112
|
+
const agent = this.agent;
|
|
1113
|
+
if (agent === undefined) {
|
|
1114
|
+
await this.adapter.sendText(target, this.t.noActiveSession);
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
const events = agent.session.events;
|
|
1118
|
+
const rows = [];
|
|
1119
|
+
for (let i = events.length - 1; i >= 0 && rows.length < limit; i--) {
|
|
1120
|
+
const e = events[i];
|
|
1121
|
+
if (e.type === "user/message") {
|
|
1122
|
+
const text = textOf(e.data);
|
|
1123
|
+
if (text !== "")
|
|
1124
|
+
rows.push(`👤 ${truncate(text, 100)}`);
|
|
1125
|
+
}
|
|
1126
|
+
else if (e.type === "assistant/message") {
|
|
1127
|
+
const text = textOf(e.data.message);
|
|
1128
|
+
if (text !== "")
|
|
1129
|
+
rows.push(`🤖 ${truncate(text, 100)}`);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
if (rows.length === 0) {
|
|
1133
|
+
await this.adapter.sendText(target, this.t.noMessagesYet);
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
await this.adapter.sendText(target, this.t.recentMessages(rows.length, rows.reverse().join("\n\n")));
|
|
1137
|
+
}
|
|
1138
|
+
async showGoals(target) {
|
|
1139
|
+
const agent = this.agent;
|
|
1140
|
+
if (agent === undefined) {
|
|
1141
|
+
await this.adapter.sendText(target, this.t.noActiveSession);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const goals = this.ctx.get("goals");
|
|
1145
|
+
const view = goals?.get?.(agent);
|
|
1146
|
+
if (view === undefined) {
|
|
1147
|
+
await this.adapter.sendText(target, this.t.noActiveGoals);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
const lines = [
|
|
1151
|
+
this.t.goalObjective(view.objective),
|
|
1152
|
+
this.t.goalStatus(this.goalPhaseLabel(view.phase)),
|
|
1153
|
+
this.t.goalRounds(view.roundsStarted, view.maxGoalRounds),
|
|
1154
|
+
this.t.goalAutoRun(view.activation === "armed"),
|
|
1155
|
+
];
|
|
1156
|
+
if (view.blockedReason !== undefined)
|
|
1157
|
+
lines.push(this.t.goalBlockedReason(view.blockedReason.message));
|
|
1158
|
+
await this.adapter.sendText(target, lines.join("\n"));
|
|
1159
|
+
}
|
|
1160
|
+
goalPhaseLabel(phase) {
|
|
1161
|
+
switch (phase) {
|
|
1162
|
+
case "active":
|
|
1163
|
+
return this.t.goalPhaseActive;
|
|
1164
|
+
case "paused":
|
|
1165
|
+
return this.t.goalPhasePaused;
|
|
1166
|
+
case "blocked":
|
|
1167
|
+
return this.t.goalPhaseBlocked;
|
|
1168
|
+
case "complete":
|
|
1169
|
+
return this.t.goalPhaseComplete;
|
|
1170
|
+
default:
|
|
1171
|
+
return phase;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
async showSchedule(target) {
|
|
1175
|
+
const agent = this.agent;
|
|
1176
|
+
if (agent === undefined) {
|
|
1177
|
+
await this.adapter.sendText(target, this.t.noActiveSession);
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
const reminders = foldReminders(agent.session.events);
|
|
1181
|
+
if (reminders.length === 0) {
|
|
1182
|
+
await this.adapter.sendText(target, this.t.noReminders);
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
const now = Date.now();
|
|
1186
|
+
const locale = this.config.language === "en" ? "en-US" : "zh-CN";
|
|
1187
|
+
const lines = reminders.map((r) => {
|
|
1188
|
+
const due = Date.parse(r.scheduledAt) <= now;
|
|
1189
|
+
const state = due ? this.t.reminderDue : this.t.reminderClock;
|
|
1190
|
+
const kind = r.kind === "every" ? this.t.reminderEvery(Math.round((r.everySeconds ?? 0) / 60)) : this.t.reminderOnce;
|
|
1191
|
+
const when = new Date(Date.parse(r.scheduledAt)).toLocaleString(locale);
|
|
1192
|
+
return this.t.reminderLine(state, r.id, r.prompt, kind, when);
|
|
1193
|
+
});
|
|
1194
|
+
await this.adapter.sendText(target, this.t.remindersCount(reminders.length, lines.join("\n")));
|
|
1195
|
+
}
|
|
1196
|
+
async showAllWorkspaces(target) {
|
|
1197
|
+
const registry = this.ctx.get("workspaceRegistry");
|
|
1198
|
+
const all = registry?.list?.() ?? [];
|
|
1199
|
+
if (all.length === 0) {
|
|
1200
|
+
await this.adapter.sendText(target, this.t.noWorkspaces);
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
const lines = all.map((w, i) => {
|
|
1204
|
+
const sess = w.sessionIds !== undefined ? this.t.workspaceSessions(w.sessionIds.length) : "";
|
|
1205
|
+
return `${i + 1}. ${w.title}${w.title !== w.path ? ` (${w.path})` : ""}${sess}`;
|
|
1206
|
+
});
|
|
1207
|
+
await this.adapter.sendText(target, this.t.workspacesCount(all.length, lines.join("\n")));
|
|
1208
|
+
}
|
|
704
1209
|
}
|
|
705
1210
|
//# sourceMappingURL=runner.js.map
|