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.
- package/LICENSE +202 -0
- package/NOTICE +11 -0
- package/README.md +476 -2
- package/README.zh-CN.md +396 -0
- package/bin/agentus.mjs +147 -0
- package/docs/android-notify-contract.md +658 -0
- package/docs/dev/01-dev-loop.md +186 -0
- package/docs/dev/02-verify.md +237 -0
- package/docs/dev/03-release.md +218 -0
- package/docs/dev/04-recover.md +199 -0
- package/docs/dev/05-agent-driven.md +114 -0
- package/docs/dev/README.md +65 -0
- package/docs/installation.md +366 -0
- package/docs/installation.zh-CN.md +350 -0
- package/docs/refs/aionui-acp-rules.md +46 -0
- package/docs/refs/hermes-studio-ui-ideas.md +191 -0
- package/docs/transcript-folding.md +139 -0
- package/package.json +76 -5
- package/packages/server/mcp/plan-server.mjs +217 -0
- package/packages/server/mock/agent.mjs +693 -0
- package/packages/server/package.json +22 -0
- package/packages/server/src/acp/backends.ts +196 -0
- package/packages/server/src/acp/errors.ts +46 -0
- package/packages/server/src/acp/registry.ts +579 -0
- package/packages/server/src/acp/session-manager.ts +1868 -0
- package/packages/server/src/auth.ts +688 -0
- package/packages/server/src/dashscope.ts +235 -0
- package/packages/server/src/fs.ts +379 -0
- package/packages/server/src/hotwords.ts +132 -0
- package/packages/server/src/index.ts +1486 -0
- package/packages/server/src/notify/center.ts +994 -0
- package/packages/server/src/notify/types.ts +222 -0
- package/packages/server/src/plan/plan.ts +126 -0
- package/packages/server/src/pty-helper.py +135 -0
- package/packages/server/src/settings.ts +469 -0
- package/packages/server/src/store/export.ts +209 -0
- package/packages/server/src/store/store.ts +1238 -0
- package/packages/server/src/term.ts +201 -0
- package/packages/server/src/title.ts +109 -0
- package/packages/server/src/voice.ts +251 -0
- package/packages/shared/package.json +14 -0
- package/packages/shared/src/index.ts +271 -0
- package/packages/web/dist/assets/index-D667PX2c.css +32 -0
- package/packages/web/dist/assets/index-d9NzwPFu.js +283 -0
- package/packages/web/dist/coding-agents/hermes.png +0 -0
- package/packages/web/dist/coding-agents/qoder.svg +5 -0
- package/packages/web/dist/icons/agentus-icon-inverse.svg +301 -0
- package/packages/web/dist/icons/agentus-icon.svg +301 -0
- package/packages/web/dist/icons/agentus-mark-inverse.svg +301 -0
- package/packages/web/dist/icons/agentus-mark.svg +301 -0
- package/packages/web/dist/icons/apple-touch-icon-180.png +0 -0
- package/packages/web/dist/icons/favicon.ico +0 -0
- package/packages/web/dist/icons/icon-192.png +0 -0
- package/packages/web/dist/icons/icon-512.png +0 -0
- package/packages/web/dist/icons/icon-maskable-512.png +0 -0
- package/packages/web/dist/index.html +30 -0
- package/packages/web/dist/manifest.webmanifest +17 -0
- package/packages/web/dist/sw.js +61 -0
- 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
|
+
}
|