dsh-connect 0.2.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/README.md +21 -0
- package/lib/commands.d.ts +4 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +26 -25
- 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 +23 -1
- 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 +33 -0
- package/lib/runner.d.ts.map +1 -1
- package/lib/runner.js +439 -129
- package/lib/runner.js.map +1 -1
- package/lib/types.d.ts +14 -0
- 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":
|
|
@@ -88,15 +83,56 @@ function textOf(message) {
|
|
|
88
83
|
.map((b) => b.text ?? "")
|
|
89
84
|
.join("");
|
|
90
85
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
+
}
|
|
100
136
|
export class AgentRunner {
|
|
101
137
|
ctx;
|
|
102
138
|
config;
|
|
@@ -113,6 +149,7 @@ export class AgentRunner {
|
|
|
113
149
|
workDir;
|
|
114
150
|
streamEnabled = true;
|
|
115
151
|
summaryEnabled = true;
|
|
152
|
+
t;
|
|
116
153
|
constructor(ctx, config, channel, chatKey, chatType, adapter, bindings) {
|
|
117
154
|
this.ctx = ctx;
|
|
118
155
|
this.config = config;
|
|
@@ -122,6 +159,51 @@ export class AgentRunner {
|
|
|
122
159
|
this.adapter = adapter;
|
|
123
160
|
this.bindings = bindings;
|
|
124
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
|
+
}
|
|
125
207
|
}
|
|
126
208
|
/** Prefer the user's first DSH workspace over the process cwd as default. */
|
|
127
209
|
resolveDefaultWorkDir() {
|
|
@@ -155,7 +237,7 @@ export class AgentRunner {
|
|
|
155
237
|
}
|
|
156
238
|
else if (chunk.type === "reasoning-delta" && !turn.reasoning) {
|
|
157
239
|
turn.reasoning = true;
|
|
158
|
-
turn.chunks.push(
|
|
240
|
+
turn.chunks.push(this.t.thinkingHint);
|
|
159
241
|
}
|
|
160
242
|
}
|
|
161
243
|
}
|
|
@@ -194,7 +276,7 @@ export class AgentRunner {
|
|
|
194
276
|
catch (error) {
|
|
195
277
|
const detail = error instanceof Error ? error.message : String(error);
|
|
196
278
|
await this.adapter
|
|
197
|
-
.sendText(this.target(msg),
|
|
279
|
+
.sendText(this.target(msg), this.t.processingFailed(truncate(detail)))
|
|
198
280
|
.catch(() => undefined);
|
|
199
281
|
}
|
|
200
282
|
}
|
|
@@ -278,11 +360,12 @@ export class AgentRunner {
|
|
|
278
360
|
}
|
|
279
361
|
async driveAgent(agent, msg) {
|
|
280
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
|
+
};
|
|
281
367
|
if (!this.streamEnabled) {
|
|
282
|
-
agent.followup(
|
|
283
|
-
content: [{ type: "text", text: msg.text }],
|
|
284
|
-
source: { kind: "user" },
|
|
285
|
-
}));
|
|
368
|
+
agent.followup(await userMessage());
|
|
286
369
|
await agent.whenIdle();
|
|
287
370
|
await this.sessions.flush(agent.session);
|
|
288
371
|
const outcome = summarizeTurn(agent.session.events, firstSeq);
|
|
@@ -294,10 +377,7 @@ export class AgentRunner {
|
|
|
294
377
|
const chunks = createAsyncQueue();
|
|
295
378
|
this.turn = { firstSeq, chunks, lastText: "", reasoning: false };
|
|
296
379
|
const streamPromise = this.adapter.streamText(this.target(msg), chunks);
|
|
297
|
-
agent.followup(
|
|
298
|
-
content: [{ type: "text", text: msg.text }],
|
|
299
|
-
source: { kind: "user" },
|
|
300
|
-
}));
|
|
380
|
+
agent.followup(await userMessage());
|
|
301
381
|
await agent.whenIdle();
|
|
302
382
|
await this.sessions.flush(agent.session);
|
|
303
383
|
chunks.end();
|
|
@@ -307,6 +387,187 @@ export class AgentRunner {
|
|
|
307
387
|
this.turn = undefined;
|
|
308
388
|
return { ...outcome, text };
|
|
309
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
|
+
}
|
|
310
571
|
readTodos() {
|
|
311
572
|
const agent = this.agent;
|
|
312
573
|
if (agent === undefined)
|
|
@@ -326,13 +587,13 @@ export class AgentRunner {
|
|
|
326
587
|
this.bindings.put({ ...binding, ownerKey: msg.senderKey, lastActiveAt: Date.now() });
|
|
327
588
|
}
|
|
328
589
|
async sendSummary(msg, outcome) {
|
|
329
|
-
const lines = [
|
|
590
|
+
const lines = [this.t.taskEnded(this.reasonLabel(outcome.reason))];
|
|
330
591
|
if (outcome.code !== undefined)
|
|
331
|
-
lines.push(
|
|
592
|
+
lines.push(this.t.errorCode(outcome.code));
|
|
332
593
|
if (outcome.message !== undefined)
|
|
333
|
-
lines.push(
|
|
594
|
+
lines.push(this.t.reasonMessage(truncate(outcome.message)));
|
|
334
595
|
if (outcome.text !== "")
|
|
335
|
-
lines.push(
|
|
596
|
+
lines.push(this.t.produced(truncate(outcome.text, 300)));
|
|
336
597
|
const card = { markdown: lines.join("\n") };
|
|
337
598
|
await this.adapter.sendCard(this.target(msg), card).catch(() => undefined);
|
|
338
599
|
}
|
|
@@ -341,17 +602,17 @@ export class AgentRunner {
|
|
|
341
602
|
switch (command.kind) {
|
|
342
603
|
case "new": {
|
|
343
604
|
await this.newChat(msg);
|
|
344
|
-
await this.adapter.sendText(target,
|
|
605
|
+
await this.adapter.sendText(target, this.t.newChatDone);
|
|
345
606
|
break;
|
|
346
607
|
}
|
|
347
608
|
case "clear": {
|
|
348
609
|
await this.clearChat(msg);
|
|
349
|
-
await this.adapter.sendText(target,
|
|
610
|
+
await this.adapter.sendText(target, this.t.chatCleared);
|
|
350
611
|
break;
|
|
351
612
|
}
|
|
352
613
|
case "stop": {
|
|
353
614
|
this.agent?.cancel({ kind: "user" });
|
|
354
|
-
await this.adapter.sendText(target,
|
|
615
|
+
await this.adapter.sendText(target, this.t.stopRequested);
|
|
355
616
|
break;
|
|
356
617
|
}
|
|
357
618
|
case "status": {
|
|
@@ -369,22 +630,22 @@ export class AgentRunner {
|
|
|
369
630
|
}
|
|
370
631
|
const path = command.path;
|
|
371
632
|
if (!isAbsolute(path)) {
|
|
372
|
-
await this.adapter.sendText(target,
|
|
633
|
+
await this.adapter.sendText(target, this.t.dirUsage);
|
|
373
634
|
break;
|
|
374
635
|
}
|
|
375
636
|
try {
|
|
376
637
|
if (!existsSync(path) || !statSync(path).isDirectory()) {
|
|
377
|
-
await this.adapter.sendText(target,
|
|
638
|
+
await this.adapter.sendText(target, this.t.dirNotExists(path));
|
|
378
639
|
break;
|
|
379
640
|
}
|
|
380
641
|
}
|
|
381
642
|
catch {
|
|
382
|
-
await this.adapter.sendText(target,
|
|
643
|
+
await this.adapter.sendText(target, this.t.dirUnreadable(path));
|
|
383
644
|
break;
|
|
384
645
|
}
|
|
385
646
|
this.workDir = path;
|
|
386
647
|
await this.newChat(msg);
|
|
387
|
-
await this.adapter.sendText(target,
|
|
648
|
+
await this.adapter.sendText(target, this.t.dirSwitched(path));
|
|
388
649
|
break;
|
|
389
650
|
}
|
|
390
651
|
case "chat": {
|
|
@@ -405,7 +666,7 @@ export class AgentRunner {
|
|
|
405
666
|
}
|
|
406
667
|
case "workspace": {
|
|
407
668
|
if (command.path === undefined) {
|
|
408
|
-
await this.adapter.sendText(target,
|
|
669
|
+
await this.adapter.sendText(target, this.t.workspaceUsage);
|
|
409
670
|
break;
|
|
410
671
|
}
|
|
411
672
|
await this.createWorkspace(command.path, target);
|
|
@@ -423,6 +684,10 @@ export class AgentRunner {
|
|
|
423
684
|
await this.showGoals(target);
|
|
424
685
|
break;
|
|
425
686
|
}
|
|
687
|
+
case "schedule": {
|
|
688
|
+
await this.showSchedule(target);
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
426
691
|
case "model": {
|
|
427
692
|
await this.openMenu(target, msg, "model", ["root"]);
|
|
428
693
|
break;
|
|
@@ -432,7 +697,7 @@ export class AgentRunner {
|
|
|
432
697
|
break;
|
|
433
698
|
}
|
|
434
699
|
case "help": {
|
|
435
|
-
await this.adapter.sendText(target,
|
|
700
|
+
await this.adapter.sendText(target, helpText(this.t));
|
|
436
701
|
break;
|
|
437
702
|
}
|
|
438
703
|
default:
|
|
@@ -506,7 +771,7 @@ export class AgentRunner {
|
|
|
506
771
|
seen.add(key);
|
|
507
772
|
out.push({ path, title: title || path });
|
|
508
773
|
};
|
|
509
|
-
add(this.workDir,
|
|
774
|
+
add(this.workDir, this.t.currentDir);
|
|
510
775
|
for (const w of registry?.list?.() ?? [])
|
|
511
776
|
add(w.path, w.title);
|
|
512
777
|
for (const p of this.config.workspaces)
|
|
@@ -516,14 +781,18 @@ export class AgentRunner {
|
|
|
516
781
|
async openMenu(target, msg, menuId, stack = [], cardId) {
|
|
517
782
|
const items = await this.menuItems(menuId);
|
|
518
783
|
const options = items.map((i) => ({ id: i.id, label: i.label }));
|
|
519
|
-
options.push({ id: "menu:exit", label:
|
|
784
|
+
options.push({ id: "menu:exit", label: this.t.menuExit });
|
|
520
785
|
if (stack.length > 0)
|
|
521
|
-
options.push({ id: "menu:back", label:
|
|
522
|
-
const { choice, messageId } = 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);
|
|
523
792
|
if (choice === undefined)
|
|
524
793
|
return;
|
|
525
794
|
if (choice === "menu:exit") {
|
|
526
|
-
await this.adapter.closeMenu(messageId,
|
|
795
|
+
await this.adapter.closeMenu(messageId, this.t.menuClosed);
|
|
527
796
|
return;
|
|
528
797
|
}
|
|
529
798
|
if (choice === "menu:back") {
|
|
@@ -544,15 +813,16 @@ export class AgentRunner {
|
|
|
544
813
|
switch (menuId) {
|
|
545
814
|
case "root":
|
|
546
815
|
return [
|
|
547
|
-
{ id: "workspace", label:
|
|
548
|
-
{ id: "chat", label:
|
|
549
|
-
{ id: "status", label:
|
|
550
|
-
{ id: "task", label:
|
|
551
|
-
{ id: "history", label:
|
|
552
|
-
{ id: "goals", label:
|
|
553
|
-
{ id: "
|
|
554
|
-
{ id: "
|
|
555
|
-
{ 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) },
|
|
556
826
|
];
|
|
557
827
|
case "workspace": {
|
|
558
828
|
const workspaces = this.listWorkspaces();
|
|
@@ -580,7 +850,7 @@ export class AgentRunner {
|
|
|
580
850
|
}));
|
|
581
851
|
items.push({
|
|
582
852
|
id: "action:new",
|
|
583
|
-
label:
|
|
853
|
+
label: this.t.menuNewChat,
|
|
584
854
|
leaf: true,
|
|
585
855
|
onSelect: async (t, m) => {
|
|
586
856
|
await this.newChat(m);
|
|
@@ -590,10 +860,10 @@ export class AgentRunner {
|
|
|
590
860
|
}
|
|
591
861
|
case "settings":
|
|
592
862
|
return [
|
|
593
|
-
{ id: "model", label:
|
|
594
|
-
{ id: "reasoning", label:
|
|
595
|
-
{ id: "notify", label:
|
|
596
|
-
{ 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); } },
|
|
597
867
|
];
|
|
598
868
|
case "model":
|
|
599
869
|
return await this.modelMenuItems();
|
|
@@ -603,7 +873,7 @@ export class AgentRunner {
|
|
|
603
873
|
return [
|
|
604
874
|
{
|
|
605
875
|
id: "stream",
|
|
606
|
-
label:
|
|
876
|
+
label: this.t.streamLabel(this.streamEnabled),
|
|
607
877
|
leaf: true,
|
|
608
878
|
onSelect: async () => {
|
|
609
879
|
this.streamEnabled = !this.streamEnabled;
|
|
@@ -611,7 +881,7 @@ export class AgentRunner {
|
|
|
611
881
|
},
|
|
612
882
|
{
|
|
613
883
|
id: "summary",
|
|
614
|
-
label:
|
|
884
|
+
label: this.t.summaryLabel(this.summaryEnabled),
|
|
615
885
|
leaf: true,
|
|
616
886
|
onSelect: async () => {
|
|
617
887
|
this.summaryEnabled = !this.summaryEnabled;
|
|
@@ -623,30 +893,40 @@ export class AgentRunner {
|
|
|
623
893
|
async showStatus(target) {
|
|
624
894
|
const agent = this.agent;
|
|
625
895
|
if (agent === undefined) {
|
|
626
|
-
await this.adapter.sendText(target,
|
|
896
|
+
await this.adapter.sendText(target, this.t.statusNoSession(this.workDir));
|
|
627
897
|
return;
|
|
628
898
|
}
|
|
629
|
-
const label = agent.status === "running" ?
|
|
899
|
+
const label = agent.status === "running" ? this.t.statusRunning : this.t.statusIdle;
|
|
630
900
|
const model = `${agent.options.provider ?? "-"}/${agent.options.model ?? "-"}`;
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
]
|
|
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"));
|
|
638
918
|
}
|
|
639
919
|
async showTasks(target) {
|
|
640
920
|
const todos = this.readTodos();
|
|
641
921
|
if (todos.length === 0) {
|
|
642
|
-
await this.adapter.sendText(target,
|
|
922
|
+
await this.adapter.sendText(target, this.t.noTodos);
|
|
643
923
|
return;
|
|
644
924
|
}
|
|
645
925
|
const lines = todos.map((todo) => {
|
|
646
926
|
const mark = todo.status === "completed" ? "✅" : todo.status === "in_progress" ? "🔄" : "⬜";
|
|
647
927
|
return `${mark} ${todo.content}`;
|
|
648
928
|
});
|
|
649
|
-
await this.adapter.sendText(target,
|
|
929
|
+
await this.adapter.sendText(target, this.t.currentTodos(todos.length, lines.join("\n")));
|
|
650
930
|
}
|
|
651
931
|
async showSettings(target) {
|
|
652
932
|
const binding = this.bindings.get(this.channel, this.chatKey);
|
|
@@ -654,21 +934,21 @@ export class AgentRunner {
|
|
|
654
934
|
const model = agent ? `${agent.options.provider ?? "-"}/${agent.options.model ?? "-"}` : "-";
|
|
655
935
|
const sel = this.defaultSelection();
|
|
656
936
|
const lines = [
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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 : ""),
|
|
663
943
|
];
|
|
664
944
|
for (const w of this.config.workspaces)
|
|
665
945
|
lines.push(` - ${w}`);
|
|
666
|
-
lines.push(
|
|
667
|
-
lines.push(
|
|
668
|
-
lines.push(
|
|
669
|
-
lines.push(
|
|
670
|
-
lines.push(
|
|
671
|
-
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"));
|
|
672
952
|
await this.adapter.sendText(target, lines.join("\n"));
|
|
673
953
|
}
|
|
674
954
|
async modelMenuItems() {
|
|
@@ -683,7 +963,7 @@ export class AgentRunner {
|
|
|
683
963
|
},
|
|
684
964
|
}));
|
|
685
965
|
if (items.length === 0) {
|
|
686
|
-
items.push({ id: "model:none", label:
|
|
966
|
+
items.push({ id: "model:none", label: this.t.noModelsFound, onSelect: async () => { } });
|
|
687
967
|
}
|
|
688
968
|
return items;
|
|
689
969
|
}
|
|
@@ -700,7 +980,7 @@ export class AgentRunner {
|
|
|
700
980
|
// Skip providers whose catalog cannot be listed.
|
|
701
981
|
}
|
|
702
982
|
for (const m of models) {
|
|
703
|
-
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) });
|
|
704
984
|
}
|
|
705
985
|
}
|
|
706
986
|
return out;
|
|
@@ -718,14 +998,14 @@ export class AgentRunner {
|
|
|
718
998
|
reasoningMenuItems() {
|
|
719
999
|
const current = this.defaultSelection();
|
|
720
1000
|
const efforts = [
|
|
721
|
-
{ id: "low", name:
|
|
722
|
-
{ id: "medium", name:
|
|
723
|
-
{ id: "high", name:
|
|
1001
|
+
{ id: "low", name: this.t.effortLow },
|
|
1002
|
+
{ id: "medium", name: this.t.effortMedium },
|
|
1003
|
+
{ id: "high", name: this.t.effortHigh },
|
|
724
1004
|
];
|
|
725
1005
|
const items = [
|
|
726
1006
|
{
|
|
727
1007
|
id: "effort:default",
|
|
728
|
-
label: `${current.reasoningEffort === undefined ? "● " : ""}
|
|
1008
|
+
label: `${current.reasoningEffort === undefined ? "● " : ""}${this.t.effortDefault}`,
|
|
729
1009
|
leaf: true,
|
|
730
1010
|
onSelect: async (t, m) => {
|
|
731
1011
|
await this.setReasoning(undefined, m);
|
|
@@ -758,80 +1038,80 @@ export class AgentRunner {
|
|
|
758
1038
|
const loader = this.ctx.get("loader");
|
|
759
1039
|
const all = [...(loader?.entries?.() ?? [])];
|
|
760
1040
|
if (all.length === 0) {
|
|
761
|
-
await this.adapter.sendText(target,
|
|
1041
|
+
await this.adapter.sendText(target, this.t.noPlugins);
|
|
762
1042
|
return;
|
|
763
1043
|
}
|
|
764
1044
|
const shown = all.slice(0, 50);
|
|
765
1045
|
const lines = shown.map((e) => {
|
|
766
|
-
const status = e.disabled ?
|
|
1046
|
+
const status = e.disabled ? this.t.pluginDisabled : this.t.pluginEnabled;
|
|
767
1047
|
return `${status} ${e.id}${e.options.name ? ` (${e.options.name})` : ""}`;
|
|
768
1048
|
});
|
|
769
1049
|
if (all.length > shown.length)
|
|
770
|
-
lines.push(
|
|
771
|
-
await this.adapter.sendText(target,
|
|
1050
|
+
lines.push(this.t.pluginsTruncated(all.length, shown.length));
|
|
1051
|
+
await this.adapter.sendText(target, this.t.pluginsCount(all.length) + lines.join("\n"));
|
|
772
1052
|
}
|
|
773
1053
|
async createWorkspace(path, target) {
|
|
774
1054
|
if (!isAbsolute(path)) {
|
|
775
|
-
await this.adapter.sendText(target,
|
|
1055
|
+
await this.adapter.sendText(target, this.t.workspaceUsage);
|
|
776
1056
|
return;
|
|
777
1057
|
}
|
|
778
1058
|
try {
|
|
779
1059
|
if (!existsSync(path) || !statSync(path).isDirectory()) {
|
|
780
|
-
await this.adapter.sendText(target,
|
|
1060
|
+
await this.adapter.sendText(target, this.t.dirNotExistsHint(path));
|
|
781
1061
|
return;
|
|
782
1062
|
}
|
|
783
1063
|
}
|
|
784
1064
|
catch {
|
|
785
|
-
await this.adapter.sendText(target,
|
|
1065
|
+
await this.adapter.sendText(target, this.t.dirUnreadable(path));
|
|
786
1066
|
return;
|
|
787
1067
|
}
|
|
788
1068
|
const registry = this.ctx.get("workspaceRegistry");
|
|
789
1069
|
if (registry?.create === undefined) {
|
|
790
|
-
await this.adapter.sendText(target,
|
|
1070
|
+
await this.adapter.sendText(target, this.t.workspaceServiceUnavailable);
|
|
791
1071
|
return;
|
|
792
1072
|
}
|
|
793
1073
|
try {
|
|
794
1074
|
const ws = await registry.create(path);
|
|
795
|
-
await this.adapter.sendText(target,
|
|
1075
|
+
await this.adapter.sendText(target, this.t.workspaceCreated(ws.title, ws.path));
|
|
796
1076
|
}
|
|
797
1077
|
catch (error) {
|
|
798
|
-
await this.adapter.sendText(target,
|
|
1078
|
+
await this.adapter.sendText(target, this.t.createFailed(error instanceof Error ? error.message : String(error)));
|
|
799
1079
|
}
|
|
800
1080
|
}
|
|
801
1081
|
async compact(target) {
|
|
802
1082
|
const agent = this.agent;
|
|
803
1083
|
if (agent === undefined) {
|
|
804
|
-
await this.adapter.sendText(target,
|
|
1084
|
+
await this.adapter.sendText(target, this.t.noActiveSessionCompact);
|
|
805
1085
|
return;
|
|
806
1086
|
}
|
|
807
1087
|
if (agent.status !== "idle") {
|
|
808
|
-
await this.adapter.sendText(target,
|
|
1088
|
+
await this.adapter.sendText(target, this.t.sessionRunning);
|
|
809
1089
|
return;
|
|
810
1090
|
}
|
|
811
1091
|
const presets = this.ctx.get("agentPresets");
|
|
812
1092
|
const compaction = presets?.serviceFor?.(agent, "compaction");
|
|
813
1093
|
if (compaction?.compactNow === undefined) {
|
|
814
|
-
await this.adapter.sendText(target,
|
|
1094
|
+
await this.adapter.sendText(target, this.t.compactionUnavailable);
|
|
815
1095
|
return;
|
|
816
1096
|
}
|
|
817
1097
|
try {
|
|
818
1098
|
const result = await compaction.compactNow(agent, new AbortController().signal);
|
|
819
1099
|
if (result === null) {
|
|
820
|
-
await this.adapter.sendText(target,
|
|
1100
|
+
await this.adapter.sendText(target, this.t.nothingToCompact);
|
|
821
1101
|
}
|
|
822
1102
|
else {
|
|
823
|
-
await this.adapter.sendText(target,
|
|
1103
|
+
await this.adapter.sendText(target, this.t.contextCompacted);
|
|
824
1104
|
}
|
|
825
1105
|
}
|
|
826
1106
|
catch (error) {
|
|
827
1107
|
const msg = error instanceof Error ? error.message : String(error);
|
|
828
|
-
await this.adapter.sendText(target,
|
|
1108
|
+
await this.adapter.sendText(target, this.t.compactFailed(truncate(msg)));
|
|
829
1109
|
}
|
|
830
1110
|
}
|
|
831
1111
|
async showHistory(target, limit) {
|
|
832
1112
|
const agent = this.agent;
|
|
833
1113
|
if (agent === undefined) {
|
|
834
|
-
await this.adapter.sendText(target,
|
|
1114
|
+
await this.adapter.sendText(target, this.t.noActiveSession);
|
|
835
1115
|
return;
|
|
836
1116
|
}
|
|
837
1117
|
const events = agent.session.events;
|
|
@@ -850,51 +1130,81 @@ export class AgentRunner {
|
|
|
850
1130
|
}
|
|
851
1131
|
}
|
|
852
1132
|
if (rows.length === 0) {
|
|
853
|
-
await this.adapter.sendText(target,
|
|
1133
|
+
await this.adapter.sendText(target, this.t.noMessagesYet);
|
|
854
1134
|
return;
|
|
855
1135
|
}
|
|
856
|
-
await this.adapter.sendText(target,
|
|
1136
|
+
await this.adapter.sendText(target, this.t.recentMessages(rows.length, rows.reverse().join("\n\n")));
|
|
857
1137
|
}
|
|
858
1138
|
async showGoals(target) {
|
|
859
1139
|
const agent = this.agent;
|
|
860
1140
|
if (agent === undefined) {
|
|
861
|
-
await this.adapter.sendText(target,
|
|
1141
|
+
await this.adapter.sendText(target, this.t.noActiveSession);
|
|
862
1142
|
return;
|
|
863
1143
|
}
|
|
864
1144
|
const goals = this.ctx.get("goals");
|
|
865
1145
|
const view = goals?.get?.(agent);
|
|
866
1146
|
if (view === undefined) {
|
|
867
|
-
await this.adapter.sendText(target,
|
|
1147
|
+
await this.adapter.sendText(target, this.t.noActiveGoals);
|
|
868
1148
|
return;
|
|
869
1149
|
}
|
|
870
|
-
const phaseLabels = {
|
|
871
|
-
active: "🟢 进行中",
|
|
872
|
-
paused: "⏸️ 已暂停",
|
|
873
|
-
blocked: "🚫 受阻",
|
|
874
|
-
complete: "✅ 已完成",
|
|
875
|
-
};
|
|
876
1150
|
const lines = [
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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"),
|
|
881
1155
|
];
|
|
882
1156
|
if (view.blockedReason !== undefined)
|
|
883
|
-
lines.push(
|
|
1157
|
+
lines.push(this.t.goalBlockedReason(view.blockedReason.message));
|
|
884
1158
|
await this.adapter.sendText(target, lines.join("\n"));
|
|
885
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
|
+
}
|
|
886
1196
|
async showAllWorkspaces(target) {
|
|
887
1197
|
const registry = this.ctx.get("workspaceRegistry");
|
|
888
1198
|
const all = registry?.list?.() ?? [];
|
|
889
1199
|
if (all.length === 0) {
|
|
890
|
-
await this.adapter.sendText(target,
|
|
1200
|
+
await this.adapter.sendText(target, this.t.noWorkspaces);
|
|
891
1201
|
return;
|
|
892
1202
|
}
|
|
893
1203
|
const lines = all.map((w, i) => {
|
|
894
|
-
const sess = w.sessionIds !== undefined ?
|
|
1204
|
+
const sess = w.sessionIds !== undefined ? this.t.workspaceSessions(w.sessionIds.length) : "";
|
|
895
1205
|
return `${i + 1}. ${w.title}${w.title !== w.path ? ` (${w.path})` : ""}${sess}`;
|
|
896
1206
|
});
|
|
897
|
-
await this.adapter.sendText(target,
|
|
1207
|
+
await this.adapter.sendText(target, this.t.workspacesCount(all.length, lines.join("\n")));
|
|
898
1208
|
}
|
|
899
1209
|
}
|
|
900
1210
|
//# sourceMappingURL=runner.js.map
|