@amanm/openpaw 0.1.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 (72) hide show
  1. package/AGENTS.md +1 -0
  2. package/README.md +144 -0
  3. package/agent/agent.ts +217 -0
  4. package/agent/context-scan.ts +81 -0
  5. package/agent/file-editor-store.ts +27 -0
  6. package/agent/index.ts +31 -0
  7. package/agent/memory-store.ts +404 -0
  8. package/agent/model.ts +14 -0
  9. package/agent/prompt-builder.ts +139 -0
  10. package/agent/prompt-context-files.ts +151 -0
  11. package/agent/sandbox-paths.ts +52 -0
  12. package/agent/session-store.ts +80 -0
  13. package/agent/skill-catalog.ts +25 -0
  14. package/agent/skills/discover.ts +100 -0
  15. package/agent/tool-stream-format.ts +126 -0
  16. package/agent/tool-yaml-like.ts +96 -0
  17. package/agent/tools/bash.ts +100 -0
  18. package/agent/tools/file-editor.ts +293 -0
  19. package/agent/tools/list-dir.ts +58 -0
  20. package/agent/tools/load-skill.ts +40 -0
  21. package/agent/tools/memory.ts +84 -0
  22. package/agent/turn-context.ts +46 -0
  23. package/agent/types.ts +37 -0
  24. package/agent/workspace-bootstrap.ts +98 -0
  25. package/bin/openpaw.cjs +177 -0
  26. package/bundled-skills/find-skills/SKILL.md +163 -0
  27. package/cli/components/chat-app.tsx +759 -0
  28. package/cli/components/onboard-ui.tsx +325 -0
  29. package/cli/components/theme.ts +16 -0
  30. package/cli/configure.tsx +0 -0
  31. package/cli/lib/chat-transcript-types.ts +11 -0
  32. package/cli/lib/markdown-render-node.ts +523 -0
  33. package/cli/lib/onboard-markdown-syntax-style.ts +55 -0
  34. package/cli/lib/ui-messages-to-chat-transcript.ts +157 -0
  35. package/cli/lib/use-auto-copy-selection.ts +38 -0
  36. package/cli/onboard.tsx +248 -0
  37. package/cli/openpaw.tsx +144 -0
  38. package/cli/reset.ts +12 -0
  39. package/cli/tui.tsx +31 -0
  40. package/config/index.ts +3 -0
  41. package/config/paths.ts +71 -0
  42. package/config/personality-copy.ts +68 -0
  43. package/config/storage.ts +80 -0
  44. package/config/types.ts +37 -0
  45. package/gateway/bootstrap.ts +25 -0
  46. package/gateway/channel-adapter.ts +8 -0
  47. package/gateway/daemon-manager.ts +191 -0
  48. package/gateway/index.ts +18 -0
  49. package/gateway/session-key.ts +13 -0
  50. package/gateway/slash-command-tokens.ts +39 -0
  51. package/gateway/start-messaging.ts +40 -0
  52. package/gateway/telegram/active-thread-store.ts +89 -0
  53. package/gateway/telegram/adapter.ts +290 -0
  54. package/gateway/telegram/assistant-markdown.ts +48 -0
  55. package/gateway/telegram/bot-commands.ts +40 -0
  56. package/gateway/telegram/chat-preferences.ts +100 -0
  57. package/gateway/telegram/constants.ts +5 -0
  58. package/gateway/telegram/index.ts +4 -0
  59. package/gateway/telegram/message-html.ts +138 -0
  60. package/gateway/telegram/message-queue.ts +19 -0
  61. package/gateway/telegram/reserved-command-filter.ts +33 -0
  62. package/gateway/telegram/session-file-discovery.ts +62 -0
  63. package/gateway/telegram/session-key.ts +13 -0
  64. package/gateway/telegram/session-label.ts +14 -0
  65. package/gateway/telegram/sessions-list-reply.ts +39 -0
  66. package/gateway/telegram/stream-delivery.ts +618 -0
  67. package/gateway/tui/constants.ts +2 -0
  68. package/gateway/tui/tui-active-thread-store.ts +103 -0
  69. package/gateway/tui/tui-session-discovery.ts +94 -0
  70. package/gateway/tui/tui-session-label.ts +22 -0
  71. package/gateway/tui/tui-sessions-list-message.ts +37 -0
  72. package/package.json +52 -0
@@ -0,0 +1,62 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { readdir, stat } from "node:fs/promises";
4
+ import { getSessionsDir } from "../../config/paths";
5
+ import { TELEGRAM_ACTIVE_THREADS_FILENAME } from "./constants";
6
+
7
+ export type TelegramSessionListEntry = {
8
+ sessionId: string;
9
+ mtimeMs: number;
10
+ };
11
+
12
+ function parseTelegramSessionFilename(
13
+ filename: string,
14
+ chatId: number,
15
+ ): string | null {
16
+ if (!filename.endsWith(".json") || filename === TELEGRAM_ACTIVE_THREADS_FILENAME) {
17
+ return null;
18
+ }
19
+ const stem = filename.slice(0, -".json".length);
20
+ const idStr = String(chatId);
21
+ const prefix = `telegram_${idStr}`;
22
+ if (stem === prefix) {
23
+ return `telegram:${chatId}`;
24
+ }
25
+ if (stem.startsWith(`${prefix}_`)) {
26
+ const suffix = stem.slice(prefix.length + 1);
27
+ if (!suffix) {
28
+ return null;
29
+ }
30
+ return `telegram:${chatId}:${suffix}`;
31
+ }
32
+ return null;
33
+ }
34
+
35
+ /**
36
+ * Lists on-disk session files for this Telegram chat, newest first.
37
+ */
38
+ export async function listTelegramSessionsForChat(
39
+ chatId: number,
40
+ ): Promise<TelegramSessionListEntry[]> {
41
+ const dir = getSessionsDir();
42
+ if (!existsSync(dir)) {
43
+ return [];
44
+ }
45
+ const names = await readdir(dir);
46
+ const entries: TelegramSessionListEntry[] = [];
47
+ for (const name of names) {
48
+ const sessionId = parseTelegramSessionFilename(name, chatId);
49
+ if (!sessionId) {
50
+ continue;
51
+ }
52
+ const path = join(dir, name);
53
+ try {
54
+ const st = await stat(path);
55
+ entries.push({ sessionId, mtimeMs: st.mtimeMs });
56
+ } catch {
57
+ continue;
58
+ }
59
+ }
60
+ entries.sort((a, b) => b.mtimeMs - a.mtimeMs);
61
+ return entries;
62
+ }
@@ -0,0 +1,13 @@
1
+ import type { Context } from "grammy";
2
+
3
+ /**
4
+ * Per-chat serialization key for the Telegram gateway (message queue).
5
+ * Persistence ids may add a thread suffix; see {@link getTelegramPersistenceSessionId} in `./active-thread-store.ts`.
6
+ */
7
+ export function telegramSessionKey(ctx: Context): string {
8
+ const id = ctx.chat?.id;
9
+ if (id === undefined) {
10
+ return "telegram:unknown";
11
+ }
12
+ return `telegram:${id}`;
13
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Human-readable label for a session row in /sessions (legacy → "main", else full thread id suffix).
3
+ */
4
+ export function formatTelegramSessionLabel(sessionId: string, chatId: number): string {
5
+ const legacy = `telegram:${chatId}`;
6
+ if (sessionId === legacy) {
7
+ return "main";
8
+ }
9
+ const prefix = `${legacy}:`;
10
+ if (sessionId.startsWith(prefix)) {
11
+ return sessionId.slice(prefix.length);
12
+ }
13
+ return sessionId;
14
+ }
@@ -0,0 +1,39 @@
1
+ import type { Context } from "grammy";
2
+ import { getTelegramPersistenceSessionId } from "./active-thread-store";
3
+ import { listTelegramSessionsForChat } from "./session-file-discovery";
4
+ import { formatTelegramSessionLabel } from "./session-label";
5
+
6
+ const MAX_LINES = 20;
7
+ const MAX_CHARS = 3500;
8
+
9
+ /**
10
+ * Sends the /sessions reply for one chat (numbered list, active marker, truncation).
11
+ */
12
+ export async function replyWithSessionsList(ctx: Context, chatId: number): Promise<void> {
13
+ const entries = await listTelegramSessionsForChat(chatId);
14
+ const active = await getTelegramPersistenceSessionId(chatId);
15
+
16
+ if (entries.length === 0) {
17
+ await ctx.reply("No saved sessions yet.");
18
+ return;
19
+ }
20
+
21
+ const lines: string[] = [];
22
+ const shown = Math.min(entries.length, MAX_LINES);
23
+ for (let i = 0; i < shown; i++) {
24
+ const e = entries[i]!;
25
+ const n = i + 1;
26
+ const mark = e.sessionId === active ? " (active)" : "";
27
+ const label = formatTelegramSessionLabel(e.sessionId, chatId);
28
+ lines.push(`${n}. ${label}${mark}`);
29
+ }
30
+ if (entries.length > MAX_LINES) {
31
+ lines.push(`…and ${entries.length - MAX_LINES} more.`);
32
+ }
33
+
34
+ let body = "Saved sessions (newest first):\n" + lines.join("\n");
35
+ if (body.length > MAX_CHARS) {
36
+ body = `${body.slice(0, MAX_CHARS - 20)}\n…(truncated)`;
37
+ }
38
+ await ctx.reply(body);
39
+ }