@comchatx/cx-topics 0.1.2 → 0.1.3

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 ADDED
@@ -0,0 +1,61 @@
1
+ Names your conversations and retrieves the messages you quote. cx-topics is one pi extension: it titles the seller's work itself, on the platform's default model and on Jev's verdict on each seller message, and gives cx one tool, `quoted`, that reads a message the seller quoted.
2
+
3
+ ## The titles
4
+
5
+ The package names and renames. It titles the conversation's first seller message and each message Jev judges new work, and rewrites a topic's title when Jev judges it no longer names the topic's work, message by message as they come and, after an install, over the whole conversation from its first message. A title is the seller's, in the seller's words and language: the title model is told it is for the seller, who reads it to find the work again. It is one line, Chinese spaced from Latin letters and figures, at most 8 words and 80 characters (`title()`); an answer with nothing left writes nothing. cx is offered no tool to name or rename, and is told nothing of titles.
6
+
7
+ A title is a pi label on a seller message: a new topic's on its first message, a rewrite as a later label on the same message. A message of no words, an image or quotes alone, gets no title of its own and stays in the topic before it. A message continues the current topic or opens a new one; none returns to an earlier topic.
8
+
9
+ **When it asks.** One Jev classify per seller message under a title. One pass at a time walks the seller messages on the branch (`getBranch`) in order, from the last one its progress names, and judges each not judged yet (`src/extensions/pass.ts`). The progress is a custom entry under `cx-topics`, `{through: <entry id>}` after each judged message and `{install: <token>}` after an install's clears; pi keeps them in the session file and puts none in the model's context.
10
+
11
+ **Where it carries on.** A progress entry naming a message no longer on the branch carries on from the branch's newest seller message, asking nothing. A first load with no progress entry and no unseen install token writes a starting entry at the branch's newest seller message and asks nothing.
12
+
13
+ **When it starts over.** Only an install starts from the first message: it clears every title the package wrote and titles the conversation again. pi tells a package nothing of how it came to be loaded, so the host records each install with a fresh token in `.pi/cx-installs.json` under the project (`INSTALLS_FILE`), keyed by the package's source with any npm version dropped (`installKey`), and the package reads a load as an install when the token for its own key is in none of its progress entries. Its key is the source pi lists for the `quoted` tool whose file is the package's own, so another package's `quoted` lends none. No key, no file, no token or a file that does not read: it carries on.
14
+
15
+ **When it titles.**
16
+ - The conversation's first seller message with words, and a message Jev judges new work: the title model titles it from the seller's message alone.
17
+ - A continuation: the same Jev call answers whether the current title still names the work in the topic. At 0.2 or less (`ROUTING_LIMITS.staleTitle`) the title model rewrites the title from that title and the topic's seller messages.
18
+ - A title is written only while the label it was asked on still stands, and only where it differs from it.
19
+
20
+ **What Jev is shown.** The topic in progress, from pi's branch: its title and the seller's own words in it, `<quote>` markers taken out, and the message judged; none of cx's words, tool calls, thinking or images. Past 31 seller messages, the newest 31 (`ROUTING_LIMITS.sellerMessages`). Each message over 512 code points and the latest over 2,048 keep their head and tail around `\n[… N characters omitted …]\n`. The topic's length sets how sure Jev must be that a message continues it: in a topic of two seller messages or fewer an even answer continues it, from eight on the confident 0.8, and between the two the bar rises evenly (`ROUTING_LIMITS`). The largest request Jev is sent is 109,260 bytes.
21
+
22
+ **The models.** Jev by pi's catalogue, `typesafe` `jev-latest` (`JEV`); the title model `balance` `minimax/minimax-m3` (`TITLE_MODEL`), named by the package rather than read from pi's settings, which hold the seller's own chat model. The title call goes through pi's nested call (`modelRegistry.complete`) with no reasoning, no tools, at most 128 output tokens and no cache retention, within 20,000 ms. The package holds no address or key: where pi has no Jev configured nothing is asked.
23
+
24
+ **Failures write nothing.** Only a result pi ends with `stop` that answers every question is a verdict, and only an answer pi ends with `stop` is a title. A call that was answered, or a request refused as it is (`400`, `413`, `422`, or a `403` without one of the gate's codes), moves on to the next message; a call that failed otherwise stops the pass at that message, to ask it again at the next seller message or the next load, never in a loop. No call is retried.
25
+
26
+ **Stop.** Stop ends cx's answer, not the filing of the seller's message: a Jev call and a title call in flight are left to finish, each on its own deadline, and the title is written.
27
+
28
+ ## `quoted`
29
+
30
+ `quoted` takes `entry`, a pi entry id. It reads pi's whole session (`getEntries`, compacted or not) and answers the quoted message with up to 2 messages of the seller's or cx's on each side: who, when, the words, and one line per tool step an answer took. The time is to the minute, on the zone of the newest quote marker naming one, else in UTC. The quoted message is cut at 8,000 characters and its neighbours at 1,000, each cut naming what it left. An id the session does not hold answers that the message is from an earlier conversation.
31
+
32
+ On every request of a session that loads the package the model is told, in the system prompt's tools section, that `quoted` reads a quoted message by the `entry` attribute of its `<quote>` tag, and in its rules section that a message holding a quote is answered after `quoted` reads it: 255 characters, about 64 tokens.
33
+
34
+ ## The quote marker
35
+
36
+ A quote inside the seller's message is sent as
37
+
38
+ ```
39
+ <quote entry="<pi entry id>" at="<epoch>:<seq>" zone="<IANA zone>">the quoted or selected words</quote>
40
+ ```
41
+
42
+ one marker per quoted message or selection, before the seller's own words. Quoted words never carry a literal `</quote>`. `entry` is what `quoted` reads; `zone` is the zone the quote's card shows its time in. Without this package the model reads the quoted words inline.
43
+
44
+ ## How it is built
45
+
46
+ - `src/core/` holds the pure parts — the protocol of Jev's questions, the routing bars and the title's cut — and imports nothing but itself.
47
+ - `src/extensions/topics.ts` is the one extension; `judge.ts` asks and writes, `pass.ts` walks the branch. `npm run build` bundles `topics.ts` into `dist/extensions/topics.js`, pi's own packages left for pi to supply; the package ships that file alone. The built module also exports `INSTALLS_FILE`, `installKey`, `JEV` and `TITLE_MODEL`, for a host to hold the package to.
48
+ - `package.json` declares no `type`. pi's loader then reads the bundle at the path pi lists the `quoted` tool's source by; declared a module, it is loaded natively and knows itself by its real path, which behind a symlink is not the path pi lists, so the package would find no key of its own and miss an install.
49
+
50
+ ## Tests
51
+
52
+ - `test/core/` — the protocol, the routing bars against a fixed comparison (`test/support/routing-comparison.json`), the title's cut, and that the core imports nothing else and the bundle carries the live files alone.
53
+ - `test/extensions/` — the title and the quote reading on pi's own session shapes, Jev's verdict and the titles, and the pass: an install's clears and its judging from the first, carrying on, a branch left, one pass at a time, a reload's cut call asked again, and the key read from pi's tool list.
54
+ - `test/support/replay.test.ts` and `replay-main.test.ts` — the calibration replay on a stand-in through pi's own registry, with no key.
55
+ - `test/package.test.ts` — the pinned pi loads the built extension with its tool and handlers; the built module exports what a host holds it to; the package ships the built extension alone.
56
+
57
+ **The calibration set.** `test/support/calibration-cases.ts`, replayed by hand by `test/support/replay-main.ts`, bundled with esbuild as `replay-main.test.ts` does (`--live --keys <env file>`): each seller message judged by the package's own judge on pi's registry, against TypeSafe and OpenRouter on development keys read from an env file by name, and held to the outcome wanted — a new topic, a continuation with its title standing or a rewrite asked. With no key it runs on a stand-in.
58
+
59
+ ## Release
60
+
61
+ Each version is published by `.github/workflows/release.yml` on a `v<version>` tag, from this repository as the package's trusted publisher on npm.
@@ -1,9 +1,9 @@
1
- // src/source-packages/cx-topics/box/extensions/topics.ts
1
+ // src/extensions/topics.ts
2
2
  import { readFileSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
6
- // src/source-packages/cx-topics/box/core/protocol.ts
6
+ // src/core/protocol.ts
7
7
  var STOPPING = /* @__PURE__ */ new Set(["refused", "timeout", "no-model"]);
8
8
  var ReviewFailure = class extends Error {
9
9
  constructor(kind) {
@@ -74,7 +74,7 @@ async function askQuestions(state, questions, ask, timeoutMs) {
74
74
  }
75
75
  }
76
76
 
77
- // src/source-packages/cx-topics/box/core/routing.ts
77
+ // src/core/routing.ts
78
78
  var ROUTING_LIMITS = Object.freeze({
79
79
  // A seller message over 512 code points, and the latest over 2,048, keeps its head and tail (`cut`).
80
80
  messageChars: 512,
@@ -96,7 +96,7 @@ var ROUTING_LIMITS = Object.freeze({
96
96
  titleMs: 2e4,
97
97
  // A title of 80 characters is under 128 tokens in any language; the cap bounds what a runaway answer costs.
98
98
  titleTokens: 128,
99
- // A topic of two seller messages or fewer is still finding its shape (Jack's example: two messages on one product's listing, then a
99
+ // A topic of two seller messages or fewer is still finding its shape (the product owner's example: two messages on one product's listing, then a
100
100
  // third on another's, is one topic), so an even answer continues it.
101
101
  shortTopic: 2,
102
102
  shortTopicBar: 0.5,
@@ -187,7 +187,7 @@ async function routeTopic(input, dependencies) {
187
187
  });
188
188
  }
189
189
 
190
- // src/source-packages/cx-topics/box/core/titling.ts
190
+ // src/core/titling.ts
191
191
  var TITLE_SYSTEM = [
192
192
  "You write the title of one topic in a seller's History: the piece of work the seller's messages ask for.",
193
193
  "The title is for the seller, who reads it to find this work again: name the work in the seller's own words, as they would write it.",
@@ -212,7 +212,7 @@ function titlePrompt(request) {
212
212
  ].join("\n");
213
213
  }
214
214
 
215
- // src/source-packages/cx-topics/box/extensions/judge.ts
215
+ // src/extensions/judge.ts
216
216
  var JEV = Object.freeze({ provider: "typesafe", model: "jev-latest" });
217
217
  var TITLE_MODEL = Object.freeze({ provider: "balance", model: "minimax/minimax-m3" });
218
218
  var CALL_MS = 2e4;
@@ -305,7 +305,7 @@ function createJudge(title2, setLabel, live = () => {
305
305
  };
306
306
  }
307
307
 
308
- // src/source-packages/cx-topics/box/extensions/pass.ts
308
+ // src/extensions/pass.ts
309
309
  var PROGRESS = "cx-topics";
310
310
  var INSTALLS_FILE = ".pi/cx-installs.json";
311
311
  function installKey(source) {
@@ -413,7 +413,7 @@ function createPass(ports) {
413
413
  };
414
414
  }
415
415
 
416
- // src/source-packages/cx-topics/box/extensions/topics.ts
416
+ // src/extensions/topics.ts
417
417
  var TITLE_WORDS = 8;
418
418
  var TITLE_CHARS = 80;
419
419
  var QUOTED_CHARS = 8e3;
@@ -538,10 +538,14 @@ function topics(pi) {
538
538
  export {
539
539
  AROUND,
540
540
  AROUND_CHARS,
541
+ INSTALLS_FILE,
542
+ JEV,
541
543
  QUOTED_CHARS,
542
544
  TITLE_CHARS,
545
+ TITLE_MODEL,
543
546
  TITLE_WORDS,
544
547
  topics as default,
548
+ installKey,
545
549
  ownKey,
546
550
  quoted,
547
551
  spaced,
package/package.json CHANGED
@@ -1,22 +1,32 @@
1
1
  {
2
2
  "name": "@comchatx/cx-topics",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Names your conversations and retrieves the messages you quote.",
5
- "author": "ComChatX",
6
5
  "license": "MIT",
6
+ "author": "ComChatX",
7
+ "homepage": "https://github.com/comchatx/cx-topics#readme",
7
8
  "repository": {
8
9
  "type": "git",
9
- "url": "git+https://github.com/HS-Jack-YZY/ComChatX.git",
10
- "directory": "src/source-packages/cx-topics"
10
+ "url": "git+https://github.com/comchatx/cx-topics.git"
11
11
  },
12
+ "bugs": "https://github.com/comchatx/cx-topics/issues",
12
13
  "keywords": [
13
14
  "pi-package"
14
15
  ],
15
16
  "pi": {
16
17
  "extensions": [
17
- "./extensions"
18
+ "./dist/extensions"
18
19
  ]
19
20
  },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "node scripts/build.mjs",
26
+ "prepack": "npm run build",
27
+ "typecheck": "tsc --noEmit",
28
+ "test": "vitest run"
29
+ },
20
30
  "comchatx": {
21
31
  "about": "为对话生成标题,并读取你引用的消息及上下文。",
22
32
  "mark": {
@@ -25,7 +35,14 @@
25
35
  "section": "extensions",
26
36
  "order": 100
27
37
  },
28
- "publishConfig": {
29
- "access": "public"
38
+ "peerDependencies": {
39
+ "@earendil-works/pi-coding-agent": "*"
40
+ },
41
+ "devDependencies": {
42
+ "@earendil-works/pi-coding-agent": "1.4.0",
43
+ "@types/node": "26.2.0",
44
+ "esbuild": "0.27.2",
45
+ "typescript": "7.0.2",
46
+ "vitest": "4.1.11"
30
47
  }
31
48
  }