macroclaw 0.26.0 → 0.28.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/package.json +1 -1
- package/src/app.test.ts +33 -71
- package/src/app.ts +14 -14
- package/src/claude.ts +1 -1
- package/src/index.ts +4 -0
- package/src/naming.test.ts +44 -0
- package/src/naming.ts +54 -0
- package/src/orchestrator.test.ts +253 -335
- package/src/orchestrator.ts +213 -169
- package/src/prompts.test.ts +255 -7
- package/src/prompts.ts +137 -21
- package/src/scheduler.test.ts +9 -6
- package/src/scheduler.ts +10 -3
package/package.json
CHANGED
package/src/app.test.ts
CHANGED
|
@@ -141,12 +141,12 @@ describe("App", () => {
|
|
|
141
141
|
expect(bot.filterHandlers.has("callback_query:data")).toBe(true);
|
|
142
142
|
});
|
|
143
143
|
|
|
144
|
-
it("registers chatid,
|
|
144
|
+
it("registers chatid, bg, and sessions commands", () => {
|
|
145
145
|
const app = new App(makeConfig());
|
|
146
146
|
const bot = app.bot as any;
|
|
147
147
|
expect(bot.commandHandlers.has("chatid")).toBe(true);
|
|
148
|
-
expect(bot.commandHandlers.has("session")).toBe(true);
|
|
149
148
|
expect(bot.commandHandlers.has("bg")).toBe(true);
|
|
149
|
+
expect(bot.commandHandlers.has("sessions")).toBe(true);
|
|
150
150
|
});
|
|
151
151
|
|
|
152
152
|
it("registers error handler", () => {
|
|
@@ -166,7 +166,7 @@ describe("App", () => {
|
|
|
166
166
|
await new Promise((r) => setTimeout(r, 50));
|
|
167
167
|
|
|
168
168
|
const claude = config.claude as Claude & { calls: CallInfo[] };
|
|
169
|
-
expect(claude.calls[0].prompt).
|
|
169
|
+
expect(claude.calls[0].prompt).toContain("<text>hello</text>");
|
|
170
170
|
});
|
|
171
171
|
|
|
172
172
|
it("ignores messages from unauthorized chats", async () => {
|
|
@@ -219,7 +219,7 @@ describe("App", () => {
|
|
|
219
219
|
await new Promise((r) => setTimeout(r, 50));
|
|
220
220
|
|
|
221
221
|
const claude = config.claude as Claude & { calls: CallInfo[] };
|
|
222
|
-
expect(claude.calls[0].prompt).
|
|
222
|
+
expect(claude.calls[0].prompt).toContain("<text>bg: research pricing</text>");
|
|
223
223
|
});
|
|
224
224
|
|
|
225
225
|
it("sends error wrapped in response", async () => {
|
|
@@ -269,7 +269,7 @@ describe("App", () => {
|
|
|
269
269
|
expect(bot.api.getFile).toHaveBeenCalledWith("large");
|
|
270
270
|
const claude = config.claude as Claude & { calls: CallInfo[] };
|
|
271
271
|
expect(claude.calls).toHaveLength(1);
|
|
272
|
-
expect(claude.calls[0].prompt).toContain("
|
|
272
|
+
expect(claude.calls[0].prompt).toContain("<file path=");
|
|
273
273
|
|
|
274
274
|
globalThis.fetch = origFetch;
|
|
275
275
|
});
|
|
@@ -297,7 +297,7 @@ describe("App", () => {
|
|
|
297
297
|
expect(bot.api.getFile).toHaveBeenCalledWith("doc-id");
|
|
298
298
|
const claude = config.claude as Claude & { calls: CallInfo[] };
|
|
299
299
|
expect(claude.calls).toHaveLength(1);
|
|
300
|
-
expect(claude.calls[0].prompt).toContain("
|
|
300
|
+
expect(claude.calls[0].prompt).toContain("<file path=");
|
|
301
301
|
|
|
302
302
|
globalThis.fetch = origFetch;
|
|
303
303
|
});
|
|
@@ -363,7 +363,7 @@ describe("App", () => {
|
|
|
363
363
|
|
|
364
364
|
const claude = config.claude as Claude & { calls: CallInfo[] };
|
|
365
365
|
expect(claude.calls).toHaveLength(1);
|
|
366
|
-
expect(claude.calls[0].prompt).
|
|
366
|
+
expect(claude.calls[0].prompt).toContain("<text>hello from voice</text>");
|
|
367
367
|
|
|
368
368
|
globalThis.fetch = origFetch;
|
|
369
369
|
});
|
|
@@ -532,7 +532,7 @@ describe("App", () => {
|
|
|
532
532
|
expect(ctx.editMessageReplyMarkup).toHaveBeenCalledWith({ reply_markup: { inline_keyboard: [[{ text: "✓ Yes", callback_data: "_noop" }]] } });
|
|
533
533
|
const claude = config.claude as Claude & { calls: CallInfo[] };
|
|
534
534
|
expect(claude.calls).toHaveLength(1);
|
|
535
|
-
expect(claude.calls[0].prompt).
|
|
535
|
+
expect(claude.calls[0].prompt).toContain('<button>Yes</button>');
|
|
536
536
|
});
|
|
537
537
|
|
|
538
538
|
it("handles _dismiss callback by removing reply markup", async () => {
|
|
@@ -576,7 +576,7 @@ describe("App", () => {
|
|
|
576
576
|
expect(ctx.editMessageReplyMarkup).toHaveBeenCalledWith({ reply_markup: { inline_keyboard: [[{ text: "✓ Opened", callback_data: "_noop" }]] } });
|
|
577
577
|
const calls = (bot.api.sendMessage as any).mock.calls;
|
|
578
578
|
const text = calls[calls.length - 1][1];
|
|
579
|
-
expect(text).toBe("
|
|
579
|
+
expect(text).toBe("Session not found or already finished.");
|
|
580
580
|
});
|
|
581
581
|
|
|
582
582
|
it("handles peek: callback by routing to orchestrator.handlePeek", async () => {
|
|
@@ -599,7 +599,7 @@ describe("App", () => {
|
|
|
599
599
|
expect(ctx.editMessageReplyMarkup).toHaveBeenCalledWith({ reply_markup: { inline_keyboard: [[{ text: "✓ Peeked", callback_data: "_noop" }]] } });
|
|
600
600
|
const calls = (bot.api.sendMessage as any).mock.calls;
|
|
601
601
|
const text = calls[calls.length - 1][1];
|
|
602
|
-
expect(text).toBe("
|
|
602
|
+
expect(text).toBe("Session not found or already finished.");
|
|
603
603
|
});
|
|
604
604
|
|
|
605
605
|
it("handles kill: callback by routing to orchestrator.handleKill", async () => {
|
|
@@ -622,7 +622,7 @@ describe("App", () => {
|
|
|
622
622
|
expect(ctx.editMessageReplyMarkup).toHaveBeenCalledWith({ reply_markup: { inline_keyboard: [[{ text: "✓ Killed", callback_data: "_noop" }]] } });
|
|
623
623
|
const calls = (bot.api.sendMessage as any).mock.calls;
|
|
624
624
|
const text = calls[calls.length - 1][1];
|
|
625
|
-
expect(text).toBe("
|
|
625
|
+
expect(text).toBe("Session not found or already finished.");
|
|
626
626
|
});
|
|
627
627
|
|
|
628
628
|
it("ignores callback_query from unauthorized chats", async () => {
|
|
@@ -676,35 +676,9 @@ describe("App", () => {
|
|
|
676
676
|
expect(ctx.reply).toHaveBeenCalledWith("Chat ID: `12345`", { parse_mode: "Markdown" });
|
|
677
677
|
});
|
|
678
678
|
|
|
679
|
-
it("/
|
|
680
|
-
const
|
|
681
|
-
const
|
|
682
|
-
const handler = bot.commandHandlers.get("session")!;
|
|
683
|
-
const ctx = { chat: { id: 12345 } };
|
|
684
|
-
|
|
685
|
-
handler(ctx);
|
|
686
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
687
|
-
|
|
688
|
-
const calls = (bot.api.sendMessage as any).mock.calls;
|
|
689
|
-
expect(calls.length).toBeGreaterThan(0);
|
|
690
|
-
const text = calls[calls.length - 1][1];
|
|
691
|
-
expect(text).toBe("Session: <code>test-session</code>");
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
it("/session is ignored for unauthorized chats", async () => {
|
|
695
|
-
const app = new App(makeConfig());
|
|
696
|
-
const bot = app.bot as any;
|
|
697
|
-
const handler = bot.commandHandlers.get("session")!;
|
|
698
|
-
const ctx = { chat: { id: 99999 } };
|
|
699
|
-
|
|
700
|
-
handler(ctx);
|
|
701
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
702
|
-
|
|
703
|
-
expect((bot.api.sendMessage as any).mock.calls.length).toBe(0);
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
it("/bg shows no agents via sendMessage when none are running", async () => {
|
|
707
|
-
const app = new App(makeConfig());
|
|
679
|
+
it("/bg without prompt sends usage hint", async () => {
|
|
680
|
+
const config = makeConfig();
|
|
681
|
+
const app = new App(config);
|
|
708
682
|
const bot = app.bot as any;
|
|
709
683
|
const handler = bot.commandHandlers.get("bg")!;
|
|
710
684
|
const ctx = { chat: { id: 12345 }, match: "" };
|
|
@@ -712,9 +686,9 @@ describe("App", () => {
|
|
|
712
686
|
handler(ctx);
|
|
713
687
|
await new Promise((r) => setTimeout(r, 50));
|
|
714
688
|
|
|
689
|
+
expect((config.claude as Claude & { calls: CallInfo[] }).calls).toHaveLength(0);
|
|
715
690
|
const calls = (bot.api.sendMessage as any).mock.calls;
|
|
716
|
-
|
|
717
|
-
expect(text).toBe("No background agents running.");
|
|
691
|
+
expect(calls[calls.length - 1][1]).toBe("Usage: /bg <prompt>");
|
|
718
692
|
});
|
|
719
693
|
|
|
720
694
|
it("/bg with prompt spawns a background agent via sendMessage", async () => {
|
|
@@ -739,46 +713,34 @@ describe("App", () => {
|
|
|
739
713
|
expect(claude.calls).toHaveLength(1);
|
|
740
714
|
});
|
|
741
715
|
|
|
742
|
-
it("/
|
|
743
|
-
const
|
|
744
|
-
sessionId: `bg-${Date.now()}`,
|
|
745
|
-
startedAt: new Date(),
|
|
746
|
-
result: new Promise(() => {}),
|
|
747
|
-
kill: mock(async () => {}),
|
|
748
|
-
}));
|
|
749
|
-
const config = makeConfig({ claude });
|
|
750
|
-
const app = new App(config);
|
|
716
|
+
it("/sessions lists running sessions via sendMessage", async () => {
|
|
717
|
+
const app = new App(makeConfig());
|
|
751
718
|
const bot = app.bot as any;
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
const spawnCtx = { chat: { id: 12345 }, match: "long task" };
|
|
755
|
-
bgHandler(spawnCtx);
|
|
756
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
719
|
+
const handler = bot.commandHandlers.get("sessions")!;
|
|
720
|
+
const ctx = { chat: { id: 12345 } };
|
|
757
721
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
722
|
+
handler(ctx);
|
|
723
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
761
724
|
|
|
762
725
|
const calls = (bot.api.sendMessage as any).mock.calls;
|
|
763
|
-
const
|
|
764
|
-
expect(
|
|
765
|
-
expect(lastText).toMatch(/\d+s/);
|
|
726
|
+
const text = calls[calls.length - 1][1];
|
|
727
|
+
expect(text).toBe("No running sessions.");
|
|
766
728
|
});
|
|
767
729
|
|
|
768
|
-
it("
|
|
769
|
-
if (existsSync(tmpSettingsDir)) rmSync(tmpSettingsDir, { recursive: true });
|
|
730
|
+
it("/sessions is ignored for unauthorized chats", async () => {
|
|
770
731
|
const app = new App(makeConfig());
|
|
771
732
|
const bot = app.bot as any;
|
|
772
|
-
const handler = bot.commandHandlers.get("
|
|
773
|
-
const ctx = { chat: { id:
|
|
733
|
+
const handler = bot.commandHandlers.get("sessions")!;
|
|
734
|
+
const ctx = { chat: { id: 99999 } };
|
|
774
735
|
|
|
775
736
|
handler(ctx);
|
|
776
737
|
await new Promise((r) => setTimeout(r, 50));
|
|
777
738
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
expect(text).toBe("Session: <code>none</code>");
|
|
739
|
+
// No sendMessage calls for unauthorized
|
|
740
|
+
expect((bot.api.sendMessage as any).mock.calls.length).toBe(0);
|
|
781
741
|
});
|
|
742
|
+
|
|
743
|
+
|
|
782
744
|
});
|
|
783
745
|
|
|
784
746
|
describe("error handler", () => {
|
|
@@ -803,8 +765,8 @@ describe("App", () => {
|
|
|
803
765
|
app.start();
|
|
804
766
|
expect(bot.api.setMyCommands).toHaveBeenCalledWith([
|
|
805
767
|
{ command: "chatid", description: "Show current chat ID" },
|
|
806
|
-
{ command: "
|
|
807
|
-
{ command: "
|
|
768
|
+
{ command: "bg", description: "Spawn a background agent" },
|
|
769
|
+
{ command: "sessions", description: "List running sessions" },
|
|
808
770
|
]);
|
|
809
771
|
});
|
|
810
772
|
});
|
package/src/app.ts
CHANGED
|
@@ -43,13 +43,13 @@ export class App {
|
|
|
43
43
|
start() {
|
|
44
44
|
log.info("Starting macroclaw...");
|
|
45
45
|
const scheduler = new Scheduler(this.#config.workspace, {
|
|
46
|
-
onJob: (name, prompt, model) => this.#orchestrator.handleCron(name, prompt, model),
|
|
46
|
+
onJob: (name, prompt, model, missed) => this.#orchestrator.handleCron(name, prompt, model, missed),
|
|
47
47
|
});
|
|
48
48
|
scheduler.start();
|
|
49
49
|
this.#bot.api.setMyCommands([
|
|
50
50
|
{ command: "chatid", description: "Show current chat ID" },
|
|
51
|
-
{ command: "
|
|
52
|
-
{ command: "
|
|
51
|
+
{ command: "bg", description: "Spawn a background agent" },
|
|
52
|
+
{ command: "sessions", description: "List running sessions" },
|
|
53
53
|
]).catch((err) => log.error({ err }, "Failed to set commands"));
|
|
54
54
|
this.#bot.start({
|
|
55
55
|
onStart: (botInfo) => {
|
|
@@ -73,22 +73,22 @@ export class App {
|
|
|
73
73
|
ctx.reply(`Chat ID: \`${ctx.chat.id}\``, { parse_mode: "Markdown" });
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
this.#bot.command("session", (ctx) => {
|
|
77
|
-
if (ctx.chat.id.toString() !== this.#config.authorizedChatId) return;
|
|
78
|
-
log.debug("Command /session");
|
|
79
|
-
this.#orchestrator.handleSessionCommand();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
76
|
this.#bot.command("bg", (ctx) => {
|
|
83
77
|
if (ctx.chat.id.toString() !== this.#config.authorizedChatId) return;
|
|
84
78
|
const prompt = ctx.match?.trim();
|
|
85
|
-
if (prompt) {
|
|
86
|
-
log.debug(
|
|
87
|
-
this.#
|
|
79
|
+
if (!prompt) {
|
|
80
|
+
log.debug("Command /bg without prompt");
|
|
81
|
+
sendResponse(this.#bot, this.#config.authorizedChatId, "Usage: /bg <prompt>");
|
|
88
82
|
return;
|
|
89
83
|
}
|
|
90
|
-
log.debug("Command /bg
|
|
91
|
-
this.#orchestrator.
|
|
84
|
+
log.debug({ prompt }, "Command /bg spawn");
|
|
85
|
+
this.#orchestrator.handleBackgroundCommand(prompt);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
this.#bot.command("sessions", (ctx) => {
|
|
89
|
+
if (ctx.chat.id.toString() !== this.#config.authorizedChatId) return;
|
|
90
|
+
log.debug("Command /sessions");
|
|
91
|
+
this.#orchestrator.handleSessions();
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
this.#bot.on("message:photo", async (ctx) => {
|
package/src/claude.ts
CHANGED
|
@@ -123,7 +123,7 @@ export class Claude {
|
|
|
123
123
|
if (systemPrompt) args.push("--append-system-prompt", systemPrompt);
|
|
124
124
|
args.push(prompt);
|
|
125
125
|
|
|
126
|
-
log.debug({ model, sessionId, promptLen: prompt.length, mode: mode.kind, hasSystemPrompt: !!systemPrompt }, "Sending to Claude");
|
|
126
|
+
log.debug({ model, sessionId, promptLen: prompt.length, mode: mode.kind, hasSystemPrompt: !!systemPrompt, prompt }, "Sending to Claude");
|
|
127
127
|
|
|
128
128
|
const proc = Bun.spawn(args, { cwd: this.#workspace, env, stdout: "pipe", stderr: "pipe" });
|
|
129
129
|
const startedAt = new Date();
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,10 @@ import { SpeechToText } from "./speech-to-text";
|
|
|
8
8
|
export async function start(): Promise<void> {
|
|
9
9
|
const log = createLogger("index");
|
|
10
10
|
|
|
11
|
+
process.on("unhandledRejection", (err) => {
|
|
12
|
+
log.error({ err }, "Unhandled rejection");
|
|
13
|
+
});
|
|
14
|
+
|
|
11
15
|
const mgr = new SettingsManager();
|
|
12
16
|
const settings = mgr.load();
|
|
13
17
|
const { settings: resolved, overrides } = mgr.applyEnvOverrides(settings);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { generateName } from "./naming";
|
|
3
|
+
|
|
4
|
+
describe("generateName", () => {
|
|
5
|
+
test("extracts content words from English prompt", () => {
|
|
6
|
+
expect(generateName("please research the best coffee shops in Prague")).toBe("research-best-coffee-shops");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("extracts content words from Czech prompt", () => {
|
|
10
|
+
expect(generateName("najdi nejlepsi kavárny v Praze a porovnej ceny")).toBe("najdi-nejlepsi-kavrny-praze");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("returns 'task' for stop-words-only prompt", () => {
|
|
14
|
+
expect(generateName("please do this for me")).toBe("task");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("returns 'task' for empty prompt", () => {
|
|
18
|
+
expect(generateName("")).toBe("task");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("strips non-alphanumeric characters", () => {
|
|
22
|
+
expect(generateName("fix bug #123 in auth-service!")).toBe("fix-bug-123-auth");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("respects maxWords parameter", () => {
|
|
26
|
+
expect(generateName("deploy new redis cluster with monitoring", 2)).toBe("deploy-new");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("skips single-character words", () => {
|
|
30
|
+
expect(generateName("a b c deploy x y z")).toBe("deploy");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("handles mixed English and Czech", () => {
|
|
34
|
+
expect(generateName("zkontroluj jestli je deploy hotovy")).toBe("zkontroluj-jestli-deploy-hotovy");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("respects maxLength and drops words that would exceed it", () => {
|
|
38
|
+
expect(generateName("deploy infrastructure monitoring cluster", 4, 25)).toBe("deploy-infrastructure");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("returns 'task' for very long single nonsense word", () => {
|
|
42
|
+
expect(generateName("a".repeat(500))).toBe("task");
|
|
43
|
+
});
|
|
44
|
+
});
|
package/src/naming.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** Generates a short kebab-case name from a prompt by extracting content words. */
|
|
2
|
+
|
|
3
|
+
const STOP_WORDS = new Set([
|
|
4
|
+
// English
|
|
5
|
+
"a", "about", "above", "after", "again", "all", "also", "am", "an", "and",
|
|
6
|
+
"any", "are", "as", "at", "be", "because", "been", "before", "being",
|
|
7
|
+
"between", "both", "but", "by", "can", "could", "did", "do", "does",
|
|
8
|
+
"doing", "down", "during", "each", "few", "for", "from", "further", "get",
|
|
9
|
+
"got", "had", "has", "have", "having", "he", "her", "here", "hers",
|
|
10
|
+
"herself", "him", "himself", "his", "how", "i", "if", "in", "into", "is",
|
|
11
|
+
"it", "its", "itself", "just", "know", "let", "like", "make", "may", "me",
|
|
12
|
+
"might", "more", "most", "must", "my", "myself", "need", "no", "nor", "not",
|
|
13
|
+
"now", "of", "off", "on", "once", "only", "or", "other", "our", "ours",
|
|
14
|
+
"ourselves", "out", "over", "own", "please", "really", "right", "same",
|
|
15
|
+
"shall", "she", "should", "so", "some", "such", "take", "than", "that",
|
|
16
|
+
"the", "their", "theirs", "them", "themselves", "then", "there", "these",
|
|
17
|
+
"they", "this", "those", "through", "to", "too", "under", "until", "up",
|
|
18
|
+
"us", "very", "want", "was", "we", "were", "what", "when", "where", "which",
|
|
19
|
+
"while", "who", "whom", "why", "will", "with", "would", "you", "your",
|
|
20
|
+
"yours", "yourself", "yourselves",
|
|
21
|
+
// Czech
|
|
22
|
+
"a", "aby", "aj", "ale", "ani", "ano", "asi", "az", "bez", "bude", "budem",
|
|
23
|
+
"budes", "by", "byl", "byla", "byli", "bylo", "byt", "ci", "co", "dal",
|
|
24
|
+
"dane", "do", "ho", "i", "ja", "jak", "jako", "je", "jeho", "jej", "jeji",
|
|
25
|
+
"jen", "jeste", "ji", "jich", "jim", "jine", "jiz", "jsem", "jses", "jsi",
|
|
26
|
+
"jsme", "jsou", "jste", "k", "kam", "kde", "kdo", "kdyz", "ke", "ktera",
|
|
27
|
+
"ktere", "kteri", "kterou", "ktery", "ma", "mam", "mate", "me", "mezi",
|
|
28
|
+
"mi", "mit", "mne", "mnou", "moc", "moje", "moji", "mu", "muze", "my",
|
|
29
|
+
"na", "nad", "nam", "nami", "nas", "nasi", "ne", "nebo", "nebot", "necht",
|
|
30
|
+
"nejsou", "neni", "nez", "nic", "nim", "o", "od", "on", "ona", "oni",
|
|
31
|
+
"ono", "pak", "po", "pod", "podle", "pokud", "potom", "pouze", "prave",
|
|
32
|
+
"pro", "proc", "proto", "protoze", "prvni", "pred", "presto", "pri", "sam",
|
|
33
|
+
"se", "si", "sice", "sve", "svou", "svuj", "svych", "svym", "svymi", "ta",
|
|
34
|
+
"tak", "take", "takze", "tato", "te", "tedy", "ten", "tento", "ti", "tim",
|
|
35
|
+
"to", "toho", "tohle", "tom", "tomu", "tu", "tuto", "tvuj", "ty", "tyto",
|
|
36
|
+
"u", "uz", "v", "vam", "vas", "vase", "ve", "vsak", "vse", "vsech",
|
|
37
|
+
"vsechno", "vsichni", "z", "za", "zda", "zde", "ze",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
export function generateName(prompt: string, maxWords = 4, maxLength = 40): string {
|
|
41
|
+
const words = prompt
|
|
42
|
+
.toLowerCase()
|
|
43
|
+
.replace(/[^a-z0-9\s-]/g, "")
|
|
44
|
+
.replace(/-/g, " ")
|
|
45
|
+
.split(/\s+/)
|
|
46
|
+
.filter((w) => w.length > 1 && !STOP_WORDS.has(w));
|
|
47
|
+
let name = "";
|
|
48
|
+
for (const word of words.slice(0, maxWords)) {
|
|
49
|
+
const candidate = name ? `${name}-${word}` : word;
|
|
50
|
+
if (candidate.length > maxLength) break;
|
|
51
|
+
name = candidate;
|
|
52
|
+
}
|
|
53
|
+
return name || "task";
|
|
54
|
+
}
|