agentus 0.0.0-stage → 0.2.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.
Files changed (59) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +11 -0
  3. package/README.md +476 -2
  4. package/README.zh-CN.md +396 -0
  5. package/bin/agentus.mjs +147 -0
  6. package/docs/android-notify-contract.md +658 -0
  7. package/docs/dev/01-dev-loop.md +186 -0
  8. package/docs/dev/02-verify.md +237 -0
  9. package/docs/dev/03-release.md +218 -0
  10. package/docs/dev/04-recover.md +199 -0
  11. package/docs/dev/05-agent-driven.md +114 -0
  12. package/docs/dev/README.md +65 -0
  13. package/docs/installation.md +366 -0
  14. package/docs/installation.zh-CN.md +350 -0
  15. package/docs/refs/aionui-acp-rules.md +46 -0
  16. package/docs/refs/hermes-studio-ui-ideas.md +191 -0
  17. package/docs/transcript-folding.md +139 -0
  18. package/package.json +76 -5
  19. package/packages/server/mcp/plan-server.mjs +217 -0
  20. package/packages/server/mock/agent.mjs +693 -0
  21. package/packages/server/package.json +22 -0
  22. package/packages/server/src/acp/backends.ts +196 -0
  23. package/packages/server/src/acp/errors.ts +46 -0
  24. package/packages/server/src/acp/registry.ts +579 -0
  25. package/packages/server/src/acp/session-manager.ts +1868 -0
  26. package/packages/server/src/auth.ts +688 -0
  27. package/packages/server/src/dashscope.ts +235 -0
  28. package/packages/server/src/fs.ts +379 -0
  29. package/packages/server/src/hotwords.ts +132 -0
  30. package/packages/server/src/index.ts +1486 -0
  31. package/packages/server/src/notify/center.ts +994 -0
  32. package/packages/server/src/notify/types.ts +222 -0
  33. package/packages/server/src/plan/plan.ts +126 -0
  34. package/packages/server/src/pty-helper.py +135 -0
  35. package/packages/server/src/settings.ts +469 -0
  36. package/packages/server/src/store/export.ts +209 -0
  37. package/packages/server/src/store/store.ts +1238 -0
  38. package/packages/server/src/term.ts +201 -0
  39. package/packages/server/src/title.ts +109 -0
  40. package/packages/server/src/voice.ts +251 -0
  41. package/packages/shared/package.json +14 -0
  42. package/packages/shared/src/index.ts +271 -0
  43. package/packages/web/dist/assets/index-D667PX2c.css +32 -0
  44. package/packages/web/dist/assets/index-d9NzwPFu.js +283 -0
  45. package/packages/web/dist/coding-agents/hermes.png +0 -0
  46. package/packages/web/dist/coding-agents/qoder.svg +5 -0
  47. package/packages/web/dist/icons/agentus-icon-inverse.svg +301 -0
  48. package/packages/web/dist/icons/agentus-icon.svg +301 -0
  49. package/packages/web/dist/icons/agentus-mark-inverse.svg +301 -0
  50. package/packages/web/dist/icons/agentus-mark.svg +301 -0
  51. package/packages/web/dist/icons/apple-touch-icon-180.png +0 -0
  52. package/packages/web/dist/icons/favicon.ico +0 -0
  53. package/packages/web/dist/icons/icon-192.png +0 -0
  54. package/packages/web/dist/icons/icon-512.png +0 -0
  55. package/packages/web/dist/icons/icon-maskable-512.png +0 -0
  56. package/packages/web/dist/index.html +30 -0
  57. package/packages/web/dist/manifest.webmanifest +17 -0
  58. package/packages/web/dist/sw.js +61 -0
  59. package/scripts/make-cert.sh +101 -0
@@ -0,0 +1,132 @@
1
+ // Hotwords: fixed + dynamic, both feeding the same place (the ASR request).
2
+ //
3
+ // DashScope gives two channels and we use them where each is strongest:
4
+ // * instant `vocabulary` ({"词": 权重}) on the streaming WebSocket — decoded-side bias,
5
+ // exactly the mechanism the operator named ("固定热词");
6
+ // * a system-message word list on the OpenAI-compatible batch route — the docs'
7
+ // "实体词表" context channel, because that surface has no vocabulary parameter.
8
+ //
9
+ // "根据上下文动态生成高频热词": the cockpit knows the real context — the session's own
10
+ // transcript (and the workspace names). We rank recent message text by term frequency
11
+ // with a cheap CJK+latin tokenizer, keep plausible terms (length, noise words, code
12
+ // identifiers), and merge them under the fixed list (fixed wins, and fixed entries may
13
+ // carry =weight, including DashScope's weight-50 super-hotwords).
14
+ import { getSettings } from "./settings.js";
15
+ import type { Store } from "./store/store.js";
16
+
17
+ export interface Hotword {
18
+ word: string;
19
+ weight: number;
20
+ origin: "fixed" | "dynamic";
21
+ }
22
+
23
+ /** Words that are frequent but useless as hotwords: they bias toward text that is
24
+ * already the default output. */
25
+ const STOP = new Set([
26
+ "的", "了", "是", "在", "我", "你", "他", "她", "它", "我们", "你们", "他们", "这", "那", "有", "没有",
27
+ "一个", "可以", "什么", "怎么", "这个", "那个", "就是", "然后", "因为", "所以", "如果", "但是",
28
+ "the", "and", "for", "with", "that", "this", "from", "have", "will", "your", "you", "are", "was",
29
+ "not", "but", "all", "can", "get", "out", "its", "into", "then", "than", "also", "use", "used",
30
+ "using", "useful", "here", "there", "when", "what", "which", "while", "please", "thanks",
31
+ ]);
32
+
33
+ /** Parse "词" or "词=4" (weight 1..5, or the super-hotword 50). */
34
+ export function parseHotword(raw: string): Hotword | null {
35
+ const t = raw.trim();
36
+ if (!t) return null;
37
+ const m = /^(.*?)(?:\s*=\s*(\d+))?$/s.exec(t);
38
+ const word = (m?.[1] ?? t).trim();
39
+ if (!word) return null;
40
+ let weight = Number(m?.[2] ?? 4);
41
+ if (!Number.isFinite(weight)) weight = 4;
42
+ weight = weight >= 50 ? 50 : Math.min(5, Math.max(1, Math.round(weight)));
43
+ return { word, weight, origin: "fixed" };
44
+ }
45
+
46
+ const WORD_RE = /[A-Za-z][A-Za-z0-9_+.-]{2,}|[一-龥]{2,8}/g;
47
+
48
+ /** Term-frequency over the operator's recent text, filtered and ranked. Returns at most
49
+ * `limit` terms; fixed hotwords always survive the cap separately. */
50
+ export function extractDynamicHotwords(texts: string[], limit = 20): Hotword[] {
51
+ const counts = new Map<string, { n: number; sample: string }>();
52
+ for (const text of texts) {
53
+ for (const m of text.matchAll(WORD_RE)) {
54
+ const raw = m[0];
55
+ const lower = raw.toLowerCase();
56
+ if (STOP.has(lower) || STOP.has(raw)) continue;
57
+ // ascii runs: require a bit of substance (a path segment, an identifier), not noise
58
+ if (/^[A-Za-z]/.test(raw)) {
59
+ if (raw.length > 24) continue;
60
+ // skip obvious code-only tokens like "return", file suffixes
61
+ if (/^[\w.-]*\.(ts|js|py|md|json|yaml|yml|sh|tsx|jsx)$/.test(lower)) continue;
62
+ } else if (raw.length > 8) {
63
+ continue;
64
+ }
65
+ const prev = counts.get(raw);
66
+ if (prev) prev.n += 1;
67
+ else counts.set(raw, { n: 1, sample: raw });
68
+ }
69
+ }
70
+ const ranked = [...counts.entries()]
71
+ .filter(([, v]) => v.n >= 2)
72
+ .sort((a, b) => b[1].n - a[1].n || a[0].localeCompare(b[0]))
73
+ .slice(0, limit);
74
+ return ranked.map(([word, v]) => ({
75
+ word,
76
+ weight: v.n >= 5 ? 5 : 4,
77
+ origin: "dynamic" as const,
78
+ }));
79
+ }
80
+
81
+ /** Recent transcript text to mine hotwords from: the given session first (its messages
82
+ * are the conversation the operator is dictating INTO), then the other sessions so a
83
+ * term used across the cockpit today also surfaces. */
84
+ export function contextTexts(store: Store, sessionId?: string, budgetChars = 24_000): string[] {
85
+ const out: string[] = [];
86
+ let used = 0;
87
+ const take = (rows: { kind: string; payload: unknown }[], cap: number): void => {
88
+ let n = 0;
89
+ for (const r of rows) {
90
+ if (n >= cap || used > budgetChars) break;
91
+ const p = r.payload as { text?: string; title?: string; content?: string };
92
+ const t = String(p?.text ?? p?.title ?? p?.content ?? "");
93
+ if (t.length > 1 && t.length < 4000) {
94
+ out.push(t);
95
+ used += t.length;
96
+ n += 1;
97
+ }
98
+ }
99
+ };
100
+ if (sessionId) take(store.messagesTail(sessionId, 60).messages, 40);
101
+ for (const s of store.listSessions(false)) {
102
+ if (s.id === sessionId) continue;
103
+ take(store.messagesTail(s.id, 12).messages, 8);
104
+ }
105
+ return out;
106
+ }
107
+
108
+ /** The full list an ASR request should carry: fixed first (they may use =50), dynamic
109
+ * filling the rest up to the configured limit. */
110
+ export function hotwordsFor(store: Store, sessionId?: string): Hotword[] {
111
+ const v = getSettings().voice;
112
+ const fixed = v.hotwords.map(parseHotword).filter((h): h is Hotword => Boolean(h));
113
+ const seen = new Set(fixed.map((h) => h.word));
114
+ const limit = Math.max(0, Math.min(200, v.hotwordLimit));
115
+ const out: Hotword[] = fixed.slice(0, limit);
116
+ if (v.dynamicHotwords) {
117
+ const dyn = extractDynamicHotwords(contextTexts(store, sessionId), Math.max(0, limit - out.length));
118
+ for (const h of dyn) {
119
+ if (seen.has(h.word)) continue;
120
+ seen.add(h.word);
121
+ out.push(h);
122
+ }
123
+ }
124
+ return out;
125
+ }
126
+
127
+ /** DashScope's instant-hotword payload: {词: 权重}. */
128
+ export function vocabularyOf(words: Hotword[]): Record<string, number> {
129
+ const out: Record<string, number> = {};
130
+ for (const h of words) out[h.word] = h.weight;
131
+ return out;
132
+ }