dsh-connect 0.6.4 → 0.6.7

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/lib/index.js CHANGED
@@ -40,7 +40,9 @@ export const Config = z.object({
40
40
  });
41
41
  /** Register the `connect` service; adapters resolve it via `inject: ["connect"]`. */
42
42
  export function apply(ctx, config = {}) {
43
- void new ConnectService(ctx, config);
43
+ // The DSH loader passes `null` for entries without an explicit config
44
+ // (and does not run schema coercion on this path), so treat it as empty.
45
+ void new ConnectService(ctx, config ?? {});
44
46
  }
45
47
  export default ConnectService;
46
48
  //# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,CAAC,MAAM,0BAA0B,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,oBAAoB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEhK,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEvD,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAgBrC,qDAAqD;AACrD,MAAM,CAAC,MAAM,IAAI,GAAG,SAAS,CAAC;AAE9B,6EAA6E;AAC7E,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,QAAQ,EAAE,UAAU,EAAE,mBAAmB,CAAC,CAAC;AAElE,wEAAwE;AACxE,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7B,qFAAqF;IACrF,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,yEAAyE;IACzE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,oEAAoE;IACpE,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC/B,uFAAuF;IACvF,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IAClE,4DAA4D;IAC5D,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACjD,qDAAqD;IACrD,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC/B,iDAAiD;IACjD,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC/B,qDAAqD;IACrD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,0EAA0E;IAC1E,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE;IACvB,6FAA6F;IAC7F,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE;IAC7B,+EAA+E;IAC/E,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChF,4GAA4G;IAC5G,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE;CAC9B,CAAC,CAAC;AAQH,qFAAqF;AACrF,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,SAAwB,EAAE;IAC5D,KAAK,IAAI,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AACvC,CAAC;AAED,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,CAAC,MAAM,0BAA0B,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,oBAAoB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEhK,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEvD,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAgBrC,qDAAqD;AACrD,MAAM,CAAC,MAAM,IAAI,GAAG,SAAS,CAAC;AAE9B,6EAA6E;AAC7E,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,QAAQ,EAAE,UAAU,EAAE,mBAAmB,CAAC,CAAC;AAElE,wEAAwE;AACxE,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7B,qFAAqF;IACrF,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,yEAAyE;IACzE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,oEAAoE;IACpE,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC/B,uFAAuF;IACvF,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IAClE,4DAA4D;IAC5D,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACjD,qDAAqD;IACrD,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC/B,iDAAiD;IACjD,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC/B,qDAAqD;IACrD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,0EAA0E;IAC1E,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE;IACvB,6FAA6F;IAC7F,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE;IAC7B,+EAA+E;IAC/E,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChF,4GAA4G;IAC5G,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE;CAC9B,CAAC,CAAC;AAQH,qFAAqF;AACrF,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,SAA+B,EAAE;IACnE,sEAAsE;IACtE,yEAAyE;IACzE,KAAK,IAAI,cAAc,CAAC,GAAG,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,eAAe,cAAc,CAAC"}
package/lib/runner.d.ts CHANGED
@@ -124,6 +124,12 @@ export declare class AgentRunner {
124
124
  enqueue(msg: InboundMessage): void;
125
125
  /** Route one live session event to the active turn's chunk queue. */
126
126
  onSessionEvent(session: Session, event: SessionEvent): void;
127
+ /**
128
+ * Proactive context-high nudge: when the turn's observed context usage
129
+ * crosses the compaction threshold, ask the user (once per turn) whether to
130
+ * compact now, instead of waiting until the turn ends to report it.
131
+ */
132
+ private maybeNudgeContext;
127
133
  private target;
128
134
  /** Completion-card target: in groups, @ the requester so they notice the result. */
129
135
  private taskTarget;
@@ -142,6 +148,21 @@ export declare class AgentRunner {
142
148
  * Best-effort: without a workspace registry this is a no-op.
143
149
  */
144
150
  private attachToWorkspace;
151
+ /**
152
+ * Historical sessions for the CURRENT work directory: this chat's binding
153
+ * records (filtered by workDir), then any other session the DSH workspace
154
+ * registry attaches to the directory (e.g. Web-created or older-chat
155
+ * sessions). Titles come from the binding record when known, otherwise from
156
+ * the host's session title service. Sorted newest first.
157
+ */
158
+ private collectWorkdirSessions;
159
+ /**
160
+ * Session ids the DSH workspace registry attaches to a work directory.
161
+ * Best-effort: without a workspace registry this returns `[]`.
162
+ */
163
+ private workspaceSessionIds;
164
+ /** Latest display title of a session via the host query service, or undefined. */
165
+ private sessionTitleOf;
145
166
  private get agents();
146
167
  private get sessions();
147
168
  private defaultSelection;
@@ -193,6 +214,12 @@ export declare class AgentRunner {
193
214
  private sendTurnStats;
194
215
  private handleCommand;
195
216
  private disposeAgent;
217
+ /**
218
+ * `/ps <note>` — append a note to the running task. Uses the agent's `steer`
219
+ * inbox: a running driver consumes the note at its next step boundary (so it
220
+ * steers the in-flight task), and an idle driver starts a new turn with it.
221
+ */
222
+ private handleAppend;
196
223
  private recordSession;
197
224
  /**
198
225
  * Automatically create a Web mirror for the session.
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
package/lib/runner.js CHANGED
@@ -349,6 +349,10 @@ function detectImageMediaType(buf) {
349
349
  return "image/gif";
350
350
  return "image/png";
351
351
  }
352
+ /** Compare two work-directory paths ignoring trailing slashes and case (Windows). */
353
+ function sameDir(a, b) {
354
+ return a.replace(/[\\/]+$/, "").toLowerCase() === b.replace(/[\\/]+$/, "").toLowerCase();
355
+ }
352
356
  /** How often the proactive progress watchdog re-checks whether a status card is due (ms). */
353
357
  const PROGRESS_WATCHDOG_CHECK_MS = 15_000;
354
358
  export class AgentRunner {
@@ -452,6 +456,13 @@ export class AgentRunner {
452
456
  });
453
457
  return;
454
458
  }
459
+ // A task is already running (or queued): tell the user they can append a
460
+ // note to the in-flight task with /ps instead of queueing a brand-new turn
461
+ // behind it. The message still queues as before.
462
+ if (this.running) {
463
+ const hint = this.t.busyHint(truncate(msg.text, 20));
464
+ void this.adapter.sendText(this.target(msg), hint).catch(() => undefined);
465
+ }
455
466
  this.queue.push(msg);
456
467
  void this.drain();
457
468
  }
@@ -464,6 +475,21 @@ export class AgentRunner {
464
475
  return;
465
476
  if (event.seq < turn.firstSeq)
466
477
  return;
478
+ // Track context usage while the turn runs so we can nudge the user to
479
+ // compact before the window fills up (deduped to one nudge per turn).
480
+ if (event.type === "request/context") {
481
+ if (typeof event.data.contextWindow === "number")
482
+ turn.contextWindow = event.data.contextWindow;
483
+ this.maybeNudgeContext(turn);
484
+ return;
485
+ }
486
+ if (event.type === "assistant/message") {
487
+ const usage = event.data.usage;
488
+ if (usage !== undefined && typeof usage.inputTokens === "number") {
489
+ turn.contextSize = usage.inputTokens;
490
+ this.maybeNudgeContext(turn);
491
+ }
492
+ }
467
493
  if (event.type === "assistant/chunk") {
468
494
  const chunk = event.data.chunk;
469
495
  // First reasoning delta = the milestone the proactive progress notice reports.
@@ -501,6 +527,45 @@ export class AgentRunner {
501
527
  applyToolCall(turn, label);
502
528
  }
503
529
  }
530
+ /**
531
+ * Proactive context-high nudge: when the turn's observed context usage
532
+ * crosses the compaction threshold, ask the user (once per turn) whether to
533
+ * compact now, instead of waiting until the turn ends to report it.
534
+ */
535
+ maybeNudgeContext(turn) {
536
+ if (turn.contextNudged)
537
+ return;
538
+ const size = turn.contextSize;
539
+ const window = turn.contextWindow;
540
+ if (size === undefined || window === undefined || window <= 0)
541
+ return;
542
+ const pct = Math.round((size / window) * 100);
543
+ if (pct < AgentRunner.COMPACT_THRESHOLD_PCT)
544
+ return;
545
+ turn.contextNudged = true;
546
+ void (async () => {
547
+ const target = { chatKey: this.chatKey, chatType: this.chatType };
548
+ const nudge = this.t.contextHighPrompt(pct);
549
+ try {
550
+ if (await this.confirmAction(target, nudge)) {
551
+ if (this.agent?.status === "running" || this.running) {
552
+ // Compaction needs an idle agent; run it right after this turn ends.
553
+ turn.compactAfterTurn = true;
554
+ await this.adapter.sendText(target, this.t.compactQueued);
555
+ }
556
+ else {
557
+ await this.compact(target);
558
+ }
559
+ }
560
+ else {
561
+ await this.adapter.sendText(target, this.t.actionCancelled);
562
+ }
563
+ }
564
+ catch (error) {
565
+ this.log(`connect: context nudge failed: ${error instanceof Error ? error.message : String(error)}`);
566
+ }
567
+ })();
568
+ }
504
569
  target(msg) {
505
570
  return {
506
571
  chatKey: this.chatKey,
@@ -548,7 +613,7 @@ export class AgentRunner {
548
613
  }
549
614
  }
550
615
  /** Present a destructive-action confirmation; true only when the user confirms. */
551
- async confirmAction(target, promptText) {
616
+ async confirmAction(target, promptText, messageId) {
552
617
  const { choice } = await this.adapter.promptChoice(target, {
553
618
  title: this.t.confirmTitle,
554
619
  description: promptText,
@@ -556,7 +621,7 @@ export class AgentRunner {
556
621
  { id: "confirm:yes", label: this.t.confirmYes },
557
622
  { id: "confirm:no", label: this.t.confirmNo },
558
623
  ],
559
- });
624
+ }, messageId);
560
625
  return choice === "confirm:yes";
561
626
  }
562
627
  async drain() {
@@ -706,6 +771,69 @@ export class AgentRunner {
706
771
  this.log(`connect: attachToWorkspace(${sessionId}) skipped: ${String(error)}`);
707
772
  }
708
773
  }
774
+ /**
775
+ * Historical sessions for the CURRENT work directory: this chat's binding
776
+ * records (filtered by workDir), then any other session the DSH workspace
777
+ * registry attaches to the directory (e.g. Web-created or older-chat
778
+ * sessions). Titles come from the binding record when known, otherwise from
779
+ * the host's session title service. Sorted newest first.
780
+ */
781
+ async collectWorkdirSessions() {
782
+ const binding = this.bindings.get(this.channel, this.chatKey);
783
+ const byId = new Map();
784
+ for (const s of binding?.sessions ?? []) {
785
+ if (sameDir(s.workDir, this.workDir))
786
+ byId.set(s.sessionId, s.title);
787
+ }
788
+ for (const sessionId of await this.workspaceSessionIds(this.workDir)) {
789
+ if (byId.has(sessionId))
790
+ continue;
791
+ const known = (binding?.sessions ?? []).find((s) => s.sessionId === sessionId);
792
+ const title = known?.title ?? (await this.sessionTitleOf(sessionId)) ?? sessionId;
793
+ byId.set(sessionId, title);
794
+ }
795
+ const lastActive = new Map();
796
+ for (const s of binding?.sessions ?? [])
797
+ lastActive.set(s.sessionId, s.lastActiveAt);
798
+ return [...byId.entries()]
799
+ .map(([sessionId, title]) => ({ sessionId, title }))
800
+ .sort((a, b) => (lastActive.get(b.sessionId) ?? 0) - (lastActive.get(a.sessionId) ?? 0));
801
+ }
802
+ /**
803
+ * Session ids the DSH workspace registry attaches to a work directory.
804
+ * Best-effort: without a workspace registry this returns `[]`.
805
+ */
806
+ async workspaceSessionIds(workDir) {
807
+ const registry = this.ctx.get("workspaceRegistry");
808
+ if (registry?.resolveByPath === undefined)
809
+ return [];
810
+ try {
811
+ const ws = await registry.resolveByPath(workDir);
812
+ return (ws?.sessionIds ?? []).map(String);
813
+ }
814
+ catch {
815
+ return [];
816
+ }
817
+ }
818
+ /** Latest display title of a session via the host query service, or undefined. */
819
+ async sessionTitleOf(sessionId) {
820
+ const query = this.ctx.get("sessionQuery");
821
+ if (query?.readTitle === undefined)
822
+ return undefined;
823
+ try {
824
+ // `readTitle` returns the folded `SessionTitleSnapshot` object in some
825
+ // host versions and the bare title string in others — accept both.
826
+ const result = await query.readTitle(sessionId);
827
+ if (typeof result === "string")
828
+ return result;
829
+ if (result !== undefined && typeof result.title === "string")
830
+ return result.title;
831
+ return undefined;
832
+ }
833
+ catch {
834
+ return undefined;
835
+ }
836
+ }
709
837
  // Service lookups must go through ctx.get(): property access (ctx.agents) is
710
838
  // fiber-scoped and rejected from the async Feishu callback with "without inject".
711
839
  get agents() {
@@ -773,7 +901,7 @@ export class AgentRunner {
773
901
  this.turn = {
774
902
  firstSeq, chunks, lastText: "",
775
903
  reasoning: false, hintPushed: false, lastIndex: undefined, pushedAny: false,
776
- startedAt: now, lastPushAt: now, toolCount: 0,
904
+ startedAt: now, lastPushAt: now, toolCount: 0, contextNudged: false, compactAfterTurn: false,
777
905
  };
778
906
  // Liveness heartbeat: while the agent is working, keep the streaming card
779
907
  // visibly alive even through long reasoning or tool-execution stretches
@@ -850,6 +978,14 @@ export class AgentRunner {
850
978
  }
851
979
  endChunks();
852
980
  await streamPromise;
981
+ // The user confirmed a compaction nudge while this turn was still running:
982
+ // the agent is idle now, so compact before summarizing.
983
+ const compactAfterTurn = this.turn.compactAfterTurn;
984
+ if (compactAfterTurn) {
985
+ await this.compact(this.target(msg)).catch((error) => {
986
+ this.log(`connect: deferred compaction failed: ${error instanceof Error ? error.message : String(error)}`);
987
+ });
988
+ }
853
989
  const outcome = summarizeTurn(agent.session.events, firstSeq);
854
990
  const text = outcome.text !== "" ? outcome.text : this.turn.lastText;
855
991
  this.turn = undefined;
@@ -1265,6 +1401,10 @@ export class AgentRunner {
1265
1401
  await this.handleExport(target, command.format);
1266
1402
  break;
1267
1403
  }
1404
+ case "ps": {
1405
+ await this.handleAppend(command.text, target, msg);
1406
+ break;
1407
+ }
1268
1408
  case "help": {
1269
1409
  await this.adapter.sendText(target, helpText(this.t));
1270
1410
  break;
@@ -1285,6 +1425,27 @@ export class AgentRunner {
1285
1425
  }
1286
1426
  await handle?.dispose().catch(() => undefined);
1287
1427
  }
1428
+ /**
1429
+ * `/ps <note>` — append a note to the running task. Uses the agent's `steer`
1430
+ * inbox: a running driver consumes the note at its next step boundary (so it
1431
+ * steers the in-flight task), and an idle driver starts a new turn with it.
1432
+ */
1433
+ async handleAppend(text, target, msg) {
1434
+ if (text === "") {
1435
+ await this.adapter.sendText(target, this.t.psUsage);
1436
+ return;
1437
+ }
1438
+ const agent = this.agent;
1439
+ if (agent === undefined) {
1440
+ await this.adapter.sendText(target, this.t.psNoActiveSession);
1441
+ return;
1442
+ }
1443
+ const note = this.t.psAppendLabel(text);
1444
+ const content = await this.buildUserContent({ ...msg, text: note });
1445
+ agent.steer(createUserMessage({ content, source: { kind: "user" } }));
1446
+ this.touchBinding(msg);
1447
+ await this.adapter.sendText(target, this.t.psReceived(text));
1448
+ }
1288
1449
  recordSession(sessionId, title, ownerKey) {
1289
1450
  const binding = this.bindings.get(this.channel, this.chatKey);
1290
1451
  const others = (binding?.sessions ?? []).filter((s) => s.sessionId !== sessionId);
@@ -1438,11 +1599,20 @@ export class AgentRunner {
1438
1599
  return;
1439
1600
  }
1440
1601
  const item = items.find((i) => i.id === choice);
1441
- if (item === undefined)
1602
+ if (item === undefined) {
1603
+ // The tap belonged to a previous card generation (e.g. a rapid second
1604
+ // tap on the old menu while the new one was still being redrawn). Don't
1605
+ // silently swallow it: redraw the current menu so the chain stays usable.
1606
+ await this.openMenu(target, msg, menuId, stack, messageId);
1442
1607
  return;
1443
- await item.onSelect(target, msg, messageId);
1608
+ }
1609
+ const feedback = await item.onSelect(target, msg, messageId);
1444
1610
  if (item.leaf === true) {
1445
- // Return to the root menu on the same card so the user can keep operating.
1611
+ // Every leaf press must answer visibly: send the action's result feedback
1612
+ // (if any) before returning to the root menu on the same card.
1613
+ if (feedback !== undefined && feedback !== "") {
1614
+ await this.adapter.sendText(target, feedback).catch(() => undefined);
1615
+ }
1446
1616
  await this.openMenu(target, msg, "root", [], messageId);
1447
1617
  }
1448
1618
  }
@@ -1470,39 +1640,47 @@ export class AgentRunner {
1470
1640
  onSelect: async (t, m) => {
1471
1641
  this.workDir = w.path;
1472
1642
  await this.newChat(m);
1643
+ return this.t.dirSwitched(w.path);
1473
1644
  },
1474
1645
  }));
1475
1646
  }
1476
1647
  case "chat": {
1477
1648
  const binding = this.bindings.get(this.channel, this.chatKey);
1478
- const sessions = binding?.sessions ?? [];
1479
1649
  const active = binding?.sessionId;
1480
1650
  const hasWebMirror = binding?.webMirrorSessionId !== undefined;
1481
- const items = sessions.map((s) => {
1482
- // Check if this session is mirrored to Web
1483
- const isMirrored = hasWebMirror && binding.webMirrorSessionId === s.sessionId;
1651
+ const items = (await this.collectWorkdirSessions()).map(({ sessionId, title }) => {
1652
+ const isMirrored = hasWebMirror && binding?.webMirrorSessionId === sessionId;
1484
1653
  const mirrorIndicator = isMirrored ? ` ${this.t.webMirrorIndicator}` : "";
1485
- const activeIndicator = s.sessionId === active ? "●" : "○";
1654
+ const activeIndicator = sessionId === active ? "●" : "○";
1486
1655
  return {
1487
- id: `session:${s.sessionId}`,
1488
- label: `${activeIndicator} ${s.title || s.sessionId}${mirrorIndicator}`,
1656
+ id: `session:${sessionId}`,
1657
+ label: `${activeIndicator} ${title || sessionId}${mirrorIndicator}`,
1489
1658
  leaf: true,
1490
1659
  onSelect: async (t, m) => {
1491
- await this.switchTo(s.sessionId, m.senderKey);
1660
+ await this.switchTo(sessionId, m.senderKey);
1661
+ return this.t.sessionSwitched(title || sessionId);
1492
1662
  },
1493
1663
  };
1494
1664
  });
1665
+ if (items.length === 0) {
1666
+ items.push({
1667
+ id: "none:history",
1668
+ label: this.t.noSessionsInWorkdir(this.workDir),
1669
+ leaf: true,
1670
+ onSelect: async () => this.t.noSessionsInWorkdir(this.workDir),
1671
+ });
1672
+ }
1495
1673
  items.push({
1496
1674
  id: "action:new",
1497
1675
  label: this.t.menuNewChat,
1498
1676
  leaf: true,
1499
- onSelect: async (t, m) => {
1500
- if (await this.confirmAction(t, this.t.confirmNewText)) {
1677
+ onSelect: async (t, m, messageId) => {
1678
+ // Reuse the menu card for the confirm prompt so no stale card is left behind.
1679
+ if (await this.confirmAction(t, this.t.confirmNewText, messageId)) {
1501
1680
  await this.newChat(m);
1681
+ return this.t.newChatDone;
1502
1682
  }
1503
- else {
1504
- await this.adapter.sendText(t, this.t.actionCancelled);
1505
- }
1683
+ return this.t.actionCancelled;
1506
1684
  },
1507
1685
  });
1508
1686
  return items;
@@ -1676,10 +1854,11 @@ export class AgentRunner {
1676
1854
  leaf: true,
1677
1855
  onSelect: async (t, m) => {
1678
1856
  await this.setModel(c.provider, c.model, m);
1857
+ return this.t.modelSet(c.name);
1679
1858
  },
1680
1859
  }));
1681
1860
  if (items.length === 0) {
1682
- items.push({ id: "model:none", label: this.t.noModelsFound, onSelect: async () => { } });
1861
+ items.push({ id: "model:none", label: this.t.noModelsFound, leaf: true, onSelect: async () => this.t.noModelsFound });
1683
1862
  }
1684
1863
  return items;
1685
1864
  }
@@ -1725,6 +1904,7 @@ export class AgentRunner {
1725
1904
  leaf: true,
1726
1905
  onSelect: async (t, m) => {
1727
1906
  await this.setReasoning(undefined, m);
1907
+ return this.t.reasoningSet(this.t.effortDefault);
1728
1908
  },
1729
1909
  },
1730
1910
  ];
@@ -1735,6 +1915,7 @@ export class AgentRunner {
1735
1915
  leaf: true,
1736
1916
  onSelect: async (t, m) => {
1737
1917
  await this.setReasoning(e.id, m);
1918
+ return this.t.reasoningSet(e.name);
1738
1919
  },
1739
1920
  });
1740
1921
  }
@@ -1752,11 +1933,14 @@ export class AgentRunner {
1752
1933
  }
1753
1934
  /** Switch the user-facing language for this chat, persisted in its binding. */
1754
1935
  async setLanguage(lang, target, msg) {
1755
- if (lang === this.language)
1756
- return;
1757
- this.language = lang;
1758
- this.t = messages(lang);
1936
+ const changed = lang !== this.language;
1937
+ if (changed) {
1938
+ this.language = lang;
1939
+ this.t = messages(lang);
1940
+ }
1759
1941
  // Persist into the chat's binding (create one if the chat has no session yet).
1942
+ // Persisting again for the already-active language is harmless and keeps the
1943
+ // button press answer visible either way.
1760
1944
  const binding = this.bindings.get(this.channel, this.chatKey);
1761
1945
  if (binding !== undefined) {
1762
1946
  this.bindings.put({ ...binding, language: lang, lastActiveAt: Date.now() });
@@ -1774,7 +1958,8 @@ export class AgentRunner {
1774
1958
  sessions: [],
1775
1959
  });
1776
1960
  }
1777
- await this.adapter.sendText(target, this.t.languageSet(lang === "zh" ? this.t.languageZh : this.t.languageEn));
1961
+ const label = lang === "zh" ? this.t.languageZh : this.t.languageEn;
1962
+ await this.adapter.sendText(target, changed ? this.t.languageSet(label) : this.t.languageAlready(label));
1778
1963
  }
1779
1964
  /**
1780
1965
  * Switch the notification level for this chat (persisted in its binding and
@@ -1941,7 +2126,15 @@ export class AgentRunner {
1941
2126
  async showHistory(target, limit) {
1942
2127
  const agent = this.agent;
1943
2128
  if (agent === undefined) {
1944
- await this.adapter.sendText(target, this.t.noActiveSession);
2129
+ // No live agent: there may still be historical sessions for the current
2130
+ // work directory. List them instead of a bare "no active session".
2131
+ const sessions = await this.collectWorkdirSessions();
2132
+ if (sessions.length === 0) {
2133
+ await this.adapter.sendText(target, this.t.noSessionsInWorkdir(this.workDir));
2134
+ return;
2135
+ }
2136
+ const lines = sessions.slice(0, limit).map((s, i) => `${i + 1}. ${s.title || s.sessionId}`);
2137
+ await this.adapter.sendText(target, this.t.historySessions(sessions.length, lines.join("\n")));
1945
2138
  return;
1946
2139
  }
1947
2140
  const events = agent.session.events;