@primusclaw/personal-agent 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -13191,7 +13191,7 @@ function parseGroupLang(text8) {
13191
13191
  var NAMES2 = {
13192
13192
  zh: ["\u4E2D\u6587", "Chinese"],
13193
13193
  en: ["\u82F1\u6587", "English"],
13194
- auto: ["\u8DDF\u968F\u63D0\u95EE\u8005\u7684\u8BED\u8A00", "the language each question is asked in"]
13194
+ auto: ["\u8DDF\u968F\u63D0\u95EE\u8005\u7684\u8BED\u8A00", "the language of each question"]
13195
13195
  };
13196
13196
  function groupLanguageSet(lang, name) {
13197
13197
  const [zh, en2] = NAMES2[lang];
@@ -13882,6 +13882,7 @@ var TurnStarter = class {
13882
13882
  }
13883
13883
  ctx;
13884
13884
  running = /* @__PURE__ */ new Set();
13885
+ askers = new AskerLanguages();
13885
13886
  /** The turns that run now (a test waits for them). */
13886
13887
  settled() {
13887
13888
  return Promise.all([...this.running]).then(() => void 0);
@@ -14052,13 +14053,18 @@ var TurnStarter = class {
14052
14053
  };
14053
14054
  }
14054
14055
  /**
14055
- * The language a chat turn is to be answered in, when one was chosen (a review's answer follows its request's language): in a
14056
- * group the group's (`groupChosenLang`; the sender's own choice is not read there), in a 1:1 chat the person's.
14056
+ * The language a chat turn is to be answered in (a review's answer follows its request's language). In a 1:1 chat the one the
14057
+ * person chose, if any. In a group the group's (`groupChosenLang`; the sender's own choice is not read there); with none set, or
14058
+ * "follow the asker", the language of the @-message's own words (`writtenLang`), else of the sender's last message in this
14059
+ * group that had some (`AskerLanguages`), else none. A group's shared session holds every member's turns, and without the line
14060
+ * the model answers in the language of that history, not the asker's.
14057
14061
  */
14058
14062
  replyLang(request) {
14059
14063
  if (request.review !== void 0) return {};
14060
14064
  const { store } = this.ctx.deps;
14061
- const lang = request.chat.scope ? groupChosenLang(store, request.chat.scope) : chosenLang(store.prefs.getPrefs(request.chat.sender.id));
14065
+ const { chat } = request;
14066
+ const asker = chat.scope ? this.askers.answer(chat.scope, chat.sender.id, request.text) : void 0;
14067
+ const lang = chat.scope ? groupChosenLang(store, chat.scope) ?? asker : chosenLang(store.prefs.getPrefs(chat.sender.id));
14062
14068
  return lang === void 0 ? {} : { replyLang: lang };
14063
14069
  }
14064
14070
  hooks(request, run, turn, turnId) {
@@ -14130,6 +14136,24 @@ var TurnStarter = class {
14130
14136
  if (created && run.repo) store.repoHistory.clearPendingRepo(userId);
14131
14137
  }
14132
14138
  };
14139
+ var ASKERS_MAX = 1e4;
14140
+ var AskerLanguages = class {
14141
+ last = /* @__PURE__ */ new Map();
14142
+ /** The language of `text` when it has words (and it is remembered), else the one remembered for `userId` in `scope`. */
14143
+ answer(scope, userId, text8) {
14144
+ const key = `${scope}
14145
+ ${userId}`;
14146
+ const own3 = writtenLang(text8);
14147
+ if (own3 === void 0) return this.last.get(key);
14148
+ this.last.delete(key);
14149
+ this.last.set(key, own3);
14150
+ if (this.last.size > ASKERS_MAX) {
14151
+ const [oldest] = this.last.keys();
14152
+ if (oldest !== void 0) this.last.delete(oldest);
14153
+ }
14154
+ return own3;
14155
+ }
14156
+ };
14133
14157
 
14134
14158
  // packages/core/src/routing/personal-text.ts
14135
14159
  var ELSEWHERE = "Files are not passed on from this chat while it talks to an agent; only your words were sent.";
@@ -17575,11 +17599,17 @@ function choiceOf(event) {
17575
17599
  return { options, multiple: choice["multiple"] === true };
17576
17600
  }
17577
17601
  var REMOTE_KINDS = /* @__PURE__ */ new Set(["done", "needs_you", "error", "resolved"]);
17602
+ var TERMINAL_TOOL_NAMES = {
17603
+ claude: "Claude Code",
17604
+ codex: "Codex",
17605
+ cursor: "Cursor"
17606
+ };
17578
17607
  function resolvedOf(remote) {
17579
17608
  const resolved = record2(remote["resolved"]);
17580
17609
  const actionId = resolved?.["action_id"];
17581
17610
  return resolved && typeof actionId === "string" && actionId ? { actionId, by: field2(resolved["by"]) } : void 0;
17582
17611
  }
17612
+ var toolName = (tool2) => typeof tool2 === "string" && Object.hasOwn(TERMINAL_TOOL_NAMES, tool2) ? TERMINAL_TOOL_NAMES[tool2] : void 0;
17583
17613
  function remoteNoticeOf(event) {
17584
17614
  const remote = event["type"] === "assistant.say" ? record2(event["remote"]) : void 0;
17585
17615
  const kind = remote?.["kind"];
@@ -17594,7 +17624,7 @@ function remoteNoticeOf(event) {
17594
17624
  sessionId,
17595
17625
  ask: record2(remote["ask"]),
17596
17626
  machine: field2(remote["machine"]),
17597
- agent: field2(remote["agent"])
17627
+ agent: toolName(remote["tool"]) ?? field2(remote["agent"])
17598
17628
  };
17599
17629
  }
17600
17630
 
@@ -33219,6 +33249,7 @@ export {
33219
33249
  ApprovalCards,
33220
33250
  ApprovalClicks,
33221
33251
  ApprovalDecisions,
33252
+ AskerLanguages,
33222
33253
  AssistantActionClicks,
33223
33254
  AssistantActions,
33224
33255
  AssistantChat as AssistantChatService,
@@ -64,6 +64,7 @@ export interface RemoteNotice {
64
64
  readonly sessionId: string;
65
65
  readonly ask: Event | undefined;
66
66
  readonly machine: string;
67
+ /** Who asked, as the person knows it: the tool's name (Claude Code, Codex, Cursor) when Claw said which, else the agent's id. */
67
68
  readonly agent: string;
68
69
  readonly resolved?: {
69
70
  readonly actionId: string;
@@ -6,7 +6,7 @@
6
6
  * choice, nothing is sent, and Claw reads the language from the message as before. To the assistant the choice goes in the
7
7
  * message's `channel_context` (`reply_language`); to a plain session (a 1:1 chat without the assistant) as the line Claw's
8
8
  * assistant uses for it, after the message (`replyLanguageLine`). A group's turn carries the group's language in that line
9
- * instead (`groupChosenLang`): in a group a person's own choice is not read.
9
+ * instead (`groupChosenLang`), or with none set the language the asker writes in: in a group a person's own choice is not read.
10
10
  */
11
11
  import type { Lang } from "../locales/lang.js";
12
12
  /** What of a person's preferences says the choice (`Prefs.lang`, `Prefs.langSet`). */
@@ -101,6 +101,11 @@ export interface AssistantRemoteNotice {
101
101
  session_id: string;
102
102
  machine: string;
103
103
  agent: string;
104
+ /**
105
+ * A terminal's request (`ask.origin` "terminal"): which tool asked there (`claude`, `codex`, `cursor`), for a channel to name it as its
106
+ * users know it (Claude Code, Codex, Cursor) instead of by `agent`, the connector's id for it. Absent when the connector did not say.
107
+ */
108
+ tool?: "claude" | "codex" | "cursor";
104
109
  ask?: AssistantRemoteAsk;
105
110
  /** With `kind: "resolved"`: which request, and who ended it (`terminal`, or `timeout`, `session_end`, `connector_restart` when nobody did). */
106
111
  resolved?: {
@@ -45,8 +45,8 @@ export interface TurnRun {
45
45
  readonly lang: Lang;
46
46
  /**
47
47
  * The language the turn is to be answered in, told to Claw after the message: in a 1:1 chat the one the person chose (`/lang
48
- * zh|en`, `chosenLang`), in a group the group's (`groupChosenLang`); undefined: none was chosen, and the answer is in the language
49
- * of what they wrote. A chat turn's only: a review's follows its request.
48
+ * zh|en`, `chosenLang`), in a group the group's (`groupChosenLang`), or with none the asker's (`AskerLanguages`); undefined:
49
+ * none, and the answer is in the language of what they wrote. A chat turn's only: a review's follows its request.
50
50
  */
51
51
  readonly replyLang?: Lang;
52
52
  /** The answer gets thumbs of the worker's own (a phone, whose own thumbs never reach the worker). */
@@ -68,6 +68,7 @@ export interface StartTurn {
68
68
  export declare class TurnStarter {
69
69
  private readonly ctx;
70
70
  private readonly running;
71
+ private readonly askers;
71
72
  constructor(ctx: RouterContext);
72
73
  /** The turns that run now (a test waits for them). */
73
74
  settled(): Promise<void>;
@@ -98,8 +99,11 @@ export declare class TurnStarter {
98
99
  private sessionPart;
99
100
  private runOf;
100
101
  /**
101
- * The language a chat turn is to be answered in, when one was chosen (a review's answer follows its request's language): in a
102
- * group the group's (`groupChosenLang`; the sender's own choice is not read there), in a 1:1 chat the person's.
102
+ * The language a chat turn is to be answered in (a review's answer follows its request's language). In a 1:1 chat the one the
103
+ * person chose, if any. In a group the group's (`groupChosenLang`; the sender's own choice is not read there); with none set, or
104
+ * "follow the asker", the language of the @-message's own words (`writtenLang`), else of the sender's last message in this
105
+ * group that had some (`AskerLanguages`), else none. A group's shared session holds every member's turns, and without the line
106
+ * the model answers in the language of that history, not the asker's.
103
107
  */
104
108
  private replyLang;
105
109
  private hooks;
@@ -111,3 +115,12 @@ export declare class TurnStarter {
111
115
  */
112
116
  private recordSession;
113
117
  }
118
+ /**
119
+ * The language each member of a group last asked in there, for a message with no words of its own (`ok`, `#3`, a link): kept in
120
+ * memory, per group and member, the newest `ASKERS_MAX`. After a restart such a message gets no language until its sender writes one.
121
+ */
122
+ export declare class AskerLanguages {
123
+ private readonly last;
124
+ /** The language of `text` when it has words (and it is remembered), else the one remembered for `userId` in `scope`. */
125
+ answer(scope: string, userId: string, text: string): Lang | undefined;
126
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primusclaw/personal-agent",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "The personal agent's channel-neutral core: domain logic, ports and plugin interfaces.",
5
5
  "license": "MIT",
6
6
  "repository": {