@jcheesepkg/nanobot 0.9.1 → 0.9.2
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 +18 -18
- package/dist/agent/context.d.mts +4 -4
- package/dist/agent/context.d.mts.map +1 -1
- package/dist/agent/context.mjs +27 -28
- package/dist/agent/context.mjs.map +1 -1
- package/dist/agent/loop.d.mts +5 -3
- package/dist/agent/loop.d.mts.map +1 -1
- package/dist/agent/loop.mjs +64 -55
- package/dist/agent/loop.mjs.map +1 -1
- package/dist/agent/memory.d.mts.map +1 -1
- package/dist/agent/memory.mjs +3 -3
- package/dist/agent/memory.mjs.map +1 -1
- package/dist/agent/skills.d.mts.map +1 -1
- package/dist/agent/skills.mjs +4 -4
- package/dist/agent/skills.mjs.map +1 -1
- package/dist/agent/subagent.d.mts.map +1 -1
- package/dist/agent/subagent.mjs +22 -22
- package/dist/agent/subagent.mjs.map +1 -1
- package/dist/agent/tools/base.mjs +2 -2
- package/dist/agent/tools/base.mjs.map +1 -1
- package/dist/agent/tools/cron.d.mts +1 -1
- package/dist/agent/tools/cron.d.mts.map +1 -1
- package/dist/agent/tools/cron.mjs +11 -11
- package/dist/agent/tools/cron.mjs.map +1 -1
- package/dist/agent/tools/filesystem.d.mts +4 -4
- package/dist/agent/tools/filesystem.d.mts.map +1 -1
- package/dist/agent/tools/filesystem.mjs +20 -20
- package/dist/agent/tools/filesystem.mjs.map +1 -1
- package/dist/agent/tools/flex.d.mts +1 -1
- package/dist/agent/tools/flex.d.mts.map +1 -1
- package/dist/agent/tools/flex.mjs +112 -112
- package/dist/agent/tools/flex.mjs.map +1 -1
- package/dist/agent/tools/flex.test.mjs +60 -59
- package/dist/agent/tools/flex.test.mjs.map +1 -1
- package/dist/agent/tools/message.d.mts +1 -1
- package/dist/agent/tools/message.d.mts.map +1 -1
- package/dist/agent/tools/message.mjs +4 -4
- package/dist/agent/tools/message.mjs.map +1 -1
- package/dist/agent/tools/registry.d.mts.map +1 -1
- package/dist/agent/tools/registry.mjs +4 -4
- package/dist/agent/tools/registry.mjs.map +1 -1
- package/dist/agent/tools/shell.d.mts +1 -1
- package/dist/agent/tools/shell.mjs +4 -4
- package/dist/agent/tools/shell.mjs.map +1 -1
- package/dist/agent/tools/spawn.d.mts +1 -1
- package/dist/agent/tools/spawn.d.mts.map +1 -1
- package/dist/agent/tools/spawn.mjs +4 -4
- package/dist/agent/tools/spawn.mjs.map +1 -1
- package/dist/agent/tools/web.d.mts +2 -2
- package/dist/agent/tools/web.d.mts.map +1 -1
- package/dist/agent/tools/web.mjs +36 -36
- package/dist/agent/tools/web.mjs.map +1 -1
- package/dist/bus/events.mjs +1 -1
- package/dist/bus/events.mjs.map +1 -1
- package/dist/bus/queue.d.mts.map +1 -1
- package/dist/bus/queue.mjs.map +1 -1
- package/dist/channels/base.d.mts.map +1 -1
- package/dist/channels/base.mjs +2 -2
- package/dist/channels/base.mjs.map +1 -1
- package/dist/channels/line.d.mts +1 -0
- package/dist/channels/line.d.mts.map +1 -1
- package/dist/channels/line.mjs +65 -65
- package/dist/channels/line.mjs.map +1 -1
- package/dist/channels/line.test.mjs +26 -27
- package/dist/channels/line.test.mjs.map +1 -1
- package/dist/channels/manager.d.mts.map +1 -1
- package/dist/channels/manager.mjs +9 -9
- package/dist/channels/manager.mjs.map +1 -1
- package/dist/channels/telegram.mjs +34 -34
- package/dist/channels/telegram.mjs.map +1 -1
- package/dist/cli/index.mjs +36 -36
- package/dist/cli/index.mjs.map +1 -1
- package/dist/config/loader.d.mts.map +1 -1
- package/dist/config/loader.mjs +1 -1
- package/dist/config/loader.mjs.map +1 -1
- package/dist/config/schema.d.mts +387 -387
- package/dist/config/schema.d.mts.map +1 -1
- package/dist/config/schema.mjs +42 -42
- package/dist/config/schema.mjs.map +1 -1
- package/dist/gateway/server.d.mts.map +1 -1
- package/dist/gateway/server.mjs +48 -54
- package/dist/gateway/server.mjs.map +1 -1
- package/dist/heartbeat/service.d.mts.map +1 -1
- package/dist/heartbeat/service.mjs +8 -8
- package/dist/heartbeat/service.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/node_modules/{@jridgewell → .bun/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell}/sourcemap-codec/dist/sourcemap-codec.mjs +1 -1
- package/dist/node_modules/.bun/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs.map +1 -0
- package/dist/node_modules/{@vitest → .bun/@vitest_expect@2.1.9/node_modules/@vitest}/expect/dist/index.mjs +8 -8
- package/dist/node_modules/.bun/@vitest_expect@2.1.9/node_modules/@vitest/expect/dist/index.mjs.map +1 -0
- package/dist/node_modules/{@vitest → .bun/@vitest_pretty-format@2.1.9/node_modules/@vitest}/pretty-format/dist/index.mjs +2 -2
- package/dist/node_modules/.bun/@vitest_pretty-format@2.1.9/node_modules/@vitest/pretty-format/dist/index.mjs.map +1 -0
- package/dist/node_modules/{@vitest → .bun/@vitest_runner@2.1.9/node_modules/@vitest}/runner/dist/chunk-tasks.mjs +1 -1
- package/dist/node_modules/.bun/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/chunk-tasks.mjs.map +1 -0
- package/dist/node_modules/{@vitest → .bun/@vitest_runner@2.1.9/node_modules/@vitest}/runner/dist/index.mjs +6 -6
- package/dist/node_modules/.bun/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/index.mjs.map +1 -0
- package/dist/node_modules/{@vitest → .bun/@vitest_snapshot@2.1.9/node_modules/@vitest}/snapshot/dist/index.mjs +5 -5
- package/dist/node_modules/.bun/@vitest_snapshot@2.1.9/node_modules/@vitest/snapshot/dist/index.mjs.map +1 -0
- package/dist/node_modules/{@vitest → .bun/@vitest_spy@2.1.9/node_modules/@vitest}/spy/dist/index.mjs +2 -2
- package/dist/node_modules/.bun/@vitest_spy@2.1.9/node_modules/@vitest/spy/dist/index.mjs.map +1 -0
- package/dist/node_modules/{@vitest → .bun/@vitest_utils@2.1.9/node_modules/@vitest}/utils/dist/chunk-_commonjsHelpers.mjs +3 -3
- package/dist/node_modules/.bun/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.mjs.map +1 -0
- package/dist/node_modules/{@vitest → .bun/@vitest_utils@2.1.9/node_modules/@vitest}/utils/dist/diff.mjs +4 -4
- package/dist/node_modules/.bun/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/diff.mjs.map +1 -0
- package/dist/node_modules/{@vitest → .bun/@vitest_utils@2.1.9/node_modules/@vitest}/utils/dist/error.mjs +3 -3
- package/dist/node_modules/.bun/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/error.mjs.map +1 -0
- package/dist/node_modules/{@vitest → .bun/@vitest_utils@2.1.9/node_modules/@vitest}/utils/dist/helpers.mjs +1 -1
- package/dist/node_modules/.bun/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/helpers.mjs.map +1 -0
- package/dist/node_modules/{@vitest → .bun/@vitest_utils@2.1.9/node_modules/@vitest}/utils/dist/index.mjs +3 -3
- package/dist/node_modules/.bun/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/index.mjs.map +1 -0
- package/dist/node_modules/{@vitest → .bun/@vitest_utils@2.1.9/node_modules/@vitest}/utils/dist/source-map.mjs +1 -1
- package/dist/node_modules/.bun/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/source-map.mjs.map +1 -0
- package/dist/node_modules/{chai → .bun/chai@5.3.3/node_modules/chai}/index.mjs +1 -1
- package/dist/node_modules/.bun/chai@5.3.3/node_modules/chai/index.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/arguments.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/arguments.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/array.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/array.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/bigint.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/bigint.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/class.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/class.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/date.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/date.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/error.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/error.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/function.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/function.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/helpers.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/helpers.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/html.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/html.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/index.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/index.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/map.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/map.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/number.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/number.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/object.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/object.mjs.map +1 -0
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/promise.mjs +6 -0
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/promise.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/regexp.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/regexp.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/set.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/set.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/string.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/string.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/symbol.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/symbol.mjs.map +1 -0
- package/dist/node_modules/{loupe → .bun/loupe@3.2.1/node_modules/loupe}/lib/typedarray.mjs +1 -1
- package/dist/node_modules/.bun/loupe@3.2.1/node_modules/loupe/lib/typedarray.mjs.map +1 -0
- package/dist/node_modules/{magic-string → .bun/magic-string@0.30.21/node_modules/magic-string}/dist/magic-string.es.mjs +2 -2
- package/dist/node_modules/.bun/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.mjs.map +1 -0
- package/dist/node_modules/{@vitest/snapshot → .bun/pathe@1.1.2}/node_modules/pathe/dist/shared/pathe.ff20891b.mjs +1 -1
- package/dist/node_modules/.bun/pathe@1.1.2/node_modules/pathe/dist/shared/pathe.ff20891b.mjs.map +1 -0
- package/dist/node_modules/{tinyrainbow → .bun/tinyrainbow@1.2.0/node_modules/tinyrainbow}/dist/chunk-BVHSVHOK.mjs +1 -1
- package/dist/node_modules/.bun/tinyrainbow@1.2.0/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.mjs.map +1 -0
- package/dist/node_modules/{tinyrainbow → .bun/tinyrainbow@1.2.0/node_modules/tinyrainbow}/dist/node.mjs +1 -1
- package/dist/node_modules/.bun/tinyrainbow@1.2.0/node_modules/tinyrainbow/dist/node.mjs.map +1 -0
- package/dist/node_modules/{tinyspy → .bun/tinyspy@3.0.2/node_modules/tinyspy}/dist/index.mjs +1 -1
- package/dist/node_modules/.bun/tinyspy@3.0.2/node_modules/tinyspy/dist/index.mjs.map +1 -0
- package/dist/node_modules/{vitest → .bun/vitest@2.1.9_7700f9e9ace41f23/node_modules/vitest}/dist/chunks/_commonjsHelpers.BFTU3MAI.mjs +1 -1
- package/dist/node_modules/.bun/vitest@2.1.9_7700f9e9ace41f23/node_modules/vitest/dist/chunks/_commonjsHelpers.BFTU3MAI.mjs.map +1 -0
- package/dist/node_modules/{vitest → .bun/vitest@2.1.9_7700f9e9ace41f23/node_modules/vitest}/dist/chunks/date.W2xKR2qe.mjs +1 -1
- package/dist/node_modules/.bun/vitest@2.1.9_7700f9e9ace41f23/node_modules/vitest/dist/chunks/date.W2xKR2qe.mjs.map +1 -0
- package/dist/node_modules/{vitest → .bun/vitest@2.1.9_7700f9e9ace41f23/node_modules/vitest}/dist/chunks/utils.C8RiOc4B.mjs +2 -2
- package/dist/node_modules/.bun/vitest@2.1.9_7700f9e9ace41f23/node_modules/vitest/dist/chunks/utils.C8RiOc4B.mjs.map +1 -0
- package/dist/node_modules/{vitest → .bun/vitest@2.1.9_7700f9e9ace41f23/node_modules/vitest}/dist/chunks/vi.DgezovHB.mjs +11 -11
- package/dist/node_modules/.bun/vitest@2.1.9_7700f9e9ace41f23/node_modules/vitest/dist/chunks/vi.DgezovHB.mjs.map +1 -0
- package/dist/providers/base.d.mts +2 -2
- package/dist/providers/base.d.mts.map +1 -1
- package/dist/providers/openai-provider.d.mts.map +1 -1
- package/dist/providers/openai-provider.mjs +10 -9
- package/dist/providers/openai-provider.mjs.map +1 -1
- package/dist/providers/registry.d.mts +1 -1
- package/dist/providers/registry.d.mts.map +1 -1
- package/dist/providers/registry.mjs +99 -99
- package/dist/providers/registry.mjs.map +1 -1
- package/dist/session/manager.d.mts +2 -2
- package/dist/session/manager.d.mts.map +1 -1
- package/dist/session/manager.mjs +18 -19
- package/dist/session/manager.mjs.map +1 -1
- package/dist/utils/helpers.d.mts.map +1 -1
- package/dist/utils/helpers.mjs.map +1 -1
- package/package.json +11 -11
- package/skills/cron/SKILL.md +12 -8
- package/skills/daily-summary/SKILL.md +4 -0
- package/skills/english/SKILL.md +21 -7
- package/skills/expense/SKILL.md +11 -7
- package/skills/fortune/SKILL.md +24 -20
- package/skills/habit/SKILL.md +2 -1
- package/skills/hydration/SKILL.md +3 -0
- package/skills/memory/SKILL.md +1 -0
- package/skills/mood/SKILL.md +10 -6
- package/skills/skill-creator/SKILL.md +3 -0
- package/skills/summarize/SKILL.md +1 -0
- package/skills/weather/SKILL.md +10 -8
- package/dist/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs.map +0 -1
- package/dist/node_modules/@vitest/expect/dist/index.mjs.map +0 -1
- package/dist/node_modules/@vitest/pretty-format/dist/index.mjs.map +0 -1
- package/dist/node_modules/@vitest/runner/dist/chunk-tasks.mjs.map +0 -1
- package/dist/node_modules/@vitest/runner/dist/index.mjs.map +0 -1
- package/dist/node_modules/@vitest/snapshot/dist/index.mjs.map +0 -1
- package/dist/node_modules/@vitest/snapshot/node_modules/pathe/dist/shared/pathe.ff20891b.mjs.map +0 -1
- package/dist/node_modules/@vitest/spy/dist/index.mjs.map +0 -1
- package/dist/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.mjs.map +0 -1
- package/dist/node_modules/@vitest/utils/dist/diff.mjs.map +0 -1
- package/dist/node_modules/@vitest/utils/dist/error.mjs.map +0 -1
- package/dist/node_modules/@vitest/utils/dist/helpers.mjs.map +0 -1
- package/dist/node_modules/@vitest/utils/dist/index.mjs.map +0 -1
- package/dist/node_modules/@vitest/utils/dist/source-map.mjs.map +0 -1
- package/dist/node_modules/chai/index.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/arguments.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/array.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/bigint.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/class.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/date.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/error.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/function.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/helpers.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/html.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/index.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/map.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/number.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/object.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/promise.mjs +0 -6
- package/dist/node_modules/loupe/lib/promise.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/regexp.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/set.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/string.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/symbol.mjs.map +0 -1
- package/dist/node_modules/loupe/lib/typedarray.mjs.map +0 -1
- package/dist/node_modules/magic-string/dist/magic-string.es.mjs.map +0 -1
- package/dist/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.mjs.map +0 -1
- package/dist/node_modules/tinyrainbow/dist/node.mjs.map +0 -1
- package/dist/node_modules/tinyspy/dist/index.mjs.map +0 -1
- package/dist/node_modules/vitest/dist/chunks/_commonjsHelpers.BFTU3MAI.mjs.map +0 -1
- package/dist/node_modules/vitest/dist/chunks/date.W2xKR2qe.mjs.map +0 -1
- package/dist/node_modules/vitest/dist/chunks/utils.C8RiOc4B.mjs.map +0 -1
- package/dist/node_modules/vitest/dist/chunks/vi.DgezovHB.mjs.map +0 -1
- /package/dist/node_modules/{@vitest → .bun/@vitest_runner@2.1.9/node_modules/@vitest}/runner/dist/utils.mjs +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"line.test.mjs","names":[],"sources":["../../src/channels/line.test.ts"],"sourcesContent":["import { describe, it, expect } from \"vitest\";\nimport { LineChannel } from \"./line.js\";\nimport { MessageBus } from \"../bus/queue.js\";\n\n// Construct a minimal LineChannel for testing private methods\nconst bus = new MessageBus();\nconst channel = new LineChannel(\n { enabled: false, channelSecret: \"test\", channelAccessToken: \"test\", allowFrom: [] },\n bus,\n);\n\n// Access private methods via any\nconst ch = channel as unknown as {\n parseMessage(text: string): Array<{ type: string; text?: string; altText?: string; contents?: unknown }>;\n findJsonEnd(str: string): number;\n extractAltText(contents: Record<string, unknown>): string;\n returnFlexMessage(message: string): Array<{ type: string; altText?: string; contents?: unknown }>;\n};\n\n// ---------------------------------------------------------------------------\n// findJsonEnd\n// ---------------------------------------------------------------------------\ndescribe(\"findJsonEnd\", () => {\n it(\"finds end of simple object\", () => {\n expect(ch.findJsonEnd('{\"a\":1}')).toBe(7);\n });\n\n it(\"finds end of nested object\", () => {\n const json = '{\"a\":{\"b\":{\"c\":1}}}';\n expect(ch.findJsonEnd(json)).toBe(json.length);\n });\n\n it(\"finds end with trailing text\", () => {\n const str = '{\"type\":\"bubble\"} some trailing text';\n expect(ch.findJsonEnd(str)).toBe(17);\n });\n\n it(\"handles strings with escaped quotes\", () => {\n const json = '{\"text\":\"say \\\\\"hello\\\\\"\"}';\n expect(ch.findJsonEnd(json)).toBe(json.length);\n });\n\n it(\"handles strings with braces inside\", () => {\n const json = '{\"text\":\"{ not a real brace }\"}';\n expect(ch.findJsonEnd(json)).toBe(json.length);\n });\n\n it(\"handles escaped backslash before quote\", () => {\n // The string value is: path\\\\ (backslash is escaped, quote is not)\n const json = '{\"path\":\"C:\\\\\\\\\"}';\n expect(ch.findJsonEnd(json)).toBe(json.length);\n });\n\n it(\"returns -1 for non-object string\", () => {\n expect(ch.findJsonEnd(\"hello\")).toBe(-1);\n });\n\n it(\"returns -1 for unclosed object\", () => {\n expect(ch.findJsonEnd('{\"a\":1')).toBe(-1);\n });\n\n it(\"handles empty object\", () => {\n expect(ch.findJsonEnd(\"{}\")).toBe(2);\n });\n\n it(\"handles arrays inside objects\", () => {\n const json = '{\"items\":[1,2,3]}';\n expect(ch.findJsonEnd(json)).toBe(json.length);\n });\n});\n\n// ---------------------------------------------------------------------------\n// extractAltText\n// ---------------------------------------------------------------------------\ndescribe(\"extractAltText\", () => {\n it(\"extracts text from a simple bubble\", () => {\n const result = ch.extractAltText({\n type: \"bubble\",\n body: { type: \"box\", contents: [{ type: \"text\", text: \"Hello World\" }] },\n });\n expect(result).toBe(\"Hello World\");\n });\n\n it(\"extracts title when present\", () => {\n const result = ch.extractAltText({\n type: \"bubble\",\n title: \"Card Title\",\n body: { type: \"box\", contents: [] },\n });\n expect(result).toBe(\"Card Title\");\n });\n\n it(\"falls back to 'Flex Message' when no text found\", () => {\n const result = ch.extractAltText({ type: \"bubble\" });\n expect(result).toBe(\"Flex Message\");\n });\n\n it(\"truncates long text to 100 chars\", () => {\n const longText = \"x\".repeat(200);\n const result = ch.extractAltText({\n type: \"bubble\",\n body: { type: \"box\", contents: [{ type: \"text\", text: longText }] },\n });\n expect(result.length).toBe(100);\n });\n});\n\n// ---------------------------------------------------------------------------\n// parseMessage\n// ---------------------------------------------------------------------------\ndescribe(\"parseMessage\", () => {\n it(\"returns plain text for normal messages\", () => {\n const result = ch.parseMessage(\"Hello, world!\");\n expect(result).toHaveLength(1);\n expect(result[0].type).toBe(\"text\");\n expect(result[0].text).toBe(\"Hello, world!\");\n });\n\n it(\"returns (empty) for empty string\", () => {\n const result = ch.parseMessage(\"\");\n expect(result).toHaveLength(1);\n expect(result[0].text).toBe(\"(empty)\");\n });\n\n it(\"parses pure JSON flex message\", () => {\n const flex = JSON.stringify({\n type: \"bubble\",\n body: { type: \"box\", layout: \"vertical\", contents: [{ type: \"text\", text: \"Test\" }] },\n });\n const result = ch.parseMessage(flex);\n expect(result).toHaveLength(1);\n expect(result[0].type).toBe(\"flex\");\n expect(result[0].altText).toBe(\"Test\");\n });\n\n it(\"handles JSON with trailing text\", () => {\n const flex = JSON.stringify({\n type: \"bubble\",\n body: { type: \"box\", layout: \"vertical\", contents: [{ type: \"text\", text: \"Card\" }] },\n });\n const input = flex + \"\\n\\nHere is some extra text!\";\n const result = ch.parseMessage(input);\n expect(result).toHaveLength(2);\n expect(result[0].type).toBe(\"flex\");\n expect(result[1].type).toBe(\"text\");\n expect(result[1].text).toBe(\"Here is some extra text!\");\n });\n\n it(\"handles JSON with Japanese trailing text\", () => {\n const flex = JSON.stringify({\n type: \"bubble\",\n body: { type: \"box\", layout: \"vertical\", contents: [{ type: \"text\", text: \"運勢\" }] },\n });\n const input = flex + \"\\n\\n記録できた!今日もがんばろう。\";\n const result = ch.parseMessage(input);\n expect(result).toHaveLength(2);\n expect(result[0].type).toBe(\"flex\");\n expect(result[1].type).toBe(\"text\");\n expect(result[1].text).toBe(\"記録できた!今日もがんばろう。\");\n });\n\n it(\"handles whitespace around JSON\", () => {\n const flex = JSON.stringify({ type: \"bubble\", body: { type: \"box\", contents: [{ type: \"text\", text: \"OK\" }] } });\n const result = ch.parseMessage(\" \" + flex + \" \");\n expect(result).toHaveLength(1);\n expect(result[0].type).toBe(\"flex\");\n });\n\n it(\"falls back to text for invalid JSON starting with {\", () => {\n const result = ch.parseMessage(\"{not valid json at all}\");\n expect(result).toHaveLength(1);\n expect(result[0].type).toBe(\"text\");\n expect(result[0].text).toBe(\"{not valid json at all}\");\n });\n\n it(\"falls back to text for non-JSON curly brace text\", () => {\n const result = ch.parseMessage(\"{incomplete json\");\n expect(result).toHaveLength(1);\n expect(result[0].type).toBe(\"text\");\n });\n\n it(\"splits flex JSON with prefix and suffix text\", () => {\n const input = `完成!こんな感じ:{\"type\":\"bubble\",\"body\":{\"type\":\"box\",\"layout\":\"vertical\",\"contents\":[{\"type\":\"box\",\"layout\":\"vertical\",\"contents\":[{\"type\": \"text\",\"text\":\"🌱 水やり記録\",\"size\": \"xl\" ,\"weight\":\"bold\"},{\"type\": \"text\",\"text\":\"前回: 今日\\\\n植物たち元気?\", \"color\":\"#666666\",\"margin\":\"md\"}]},{\"type\": \"separator\",\"margin\":\"lg\"},{\"type\": \"box\",\"layout\":\"horizontal\",\"margin\":\"md\",\"spacing\":\"md\",\"contents\":[{\"type\": \"button\",\"style\": \"primary\",\"color\": \"#4CAF50\",\"action\":{\"type\": \"message\",\"label\": \"水やりした!\", \"text\": \"水やりした\"}},{\"type\": \"button\",\"style\": \"secondary\",\"action\":{\"type\": \"message\",\"label\": \"最後にいつ?\", \"text\": \"水やり確認\"}}]}]}}\\n\\n記録できた!今日水やりしたことになってる。緑色のボタンがカワイイでしょ。`;\n const result = ch.parseMessage(input);\n expect(result).toHaveLength(3);\n expect(result[0].type).toBe(\"text\");\n expect(result[0].text).toBe(\"完成!こんな感じ:\");\n expect(result[1].type).toBe(\"flex\");\n expect(result[1].altText).toBe(\"🌱 水やり記録\");\n expect(result[2].type).toBe(\"text\");\n expect(result[2].text).toBe(\"記録できた!今日水やりしたことになってる。緑色のボタンがカワイイでしょ。\");\n });\n\n it(\"splits real-world flex JSON with trailing Japanese text\", () => {\n const input = `{\"type\":\"bubble\",\"body\":{\"type\":\"box\",\"layout\":\"vertical\",\"contents\":[{\"type\":\"box\",\"layout\":\"vertical\",\"contents\":[{\"type\": \"text\",\"text\":\"🌱 水やり記録\",\"size\": \"xl\" ,\"weight\":\"bold\"},{\"type\": \"text\",\"text\":\"前回: 今日\\\\n植物たち元気?\", \"color\":\"#666666\",\"margin\":\"md\"}]},{\"type\": \"separator\",\"margin\":\"lg\"},{\"type\": \"box\",\"layout\":\"horizontal\",\"margin\":\"md\",\"spacing\":\"md\",\"contents\":[{\"type\": \"button\",\"style\": \"primary\",\"color\": \"#4CAF50\",\"action\":{\"type\": \"message\",\"label\": \"水やりした!\", \"text\": \"水やりした\"}},{\"type\": \"button\",\"style\": \"secondary\",\"action\":{\"type\": \"message\",\"label\": \"最後にいつ?\", \"text\": \"水やり確認\"}}]}]}}\\n\\n記録できた!今日水やりしたことになってる。緑色のボタンがカワイイでしょ。`;\n const result = ch.parseMessage(input);\n expect(result).toHaveLength(2);\n expect(result[0].type).toBe(\"flex\");\n expect(result[1].type).toBe(\"text\");\n expect(result[1].text).toBe(\"記録できた!今日水やりしたことになってる。緑色のボタンがカワイイでしょ。\");\n });\n});\n\n// ---------------------------------------------------------------------------\n// verifyLineSignature (exported function)\n// ---------------------------------------------------------------------------\nimport { verifyLineSignature } from \"./line.js\";\n\ndescribe(\"verifyLineSignature\", () => {\n it(\"returns true for valid signature\", () => {\n const secret = \"test-secret\";\n const body = '{\"events\":[]}';\n // Compute the expected signature\n const { createHmac } = require(\"node:crypto\");\n const expected = createHmac(\"sha256\", secret).update(body).digest(\"base64\");\n expect(verifyLineSignature(secret, body, expected)).toBe(true);\n });\n\n it(\"returns false for invalid signature\", () => {\n expect(verifyLineSignature(\"secret\", \"body\", \"bad-sig\")).toBe(false);\n });\n\n it(\"returns false for empty signature\", () => {\n expect(verifyLineSignature(\"secret\", \"body\", \"\")).toBe(false);\n });\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"line.test.mjs","names":[],"sources":["../../src/channels/line.test.ts"],"sourcesContent":["import { createHmac } from \"node:crypto\";\nimport { describe, it, expect } from \"vitest\";\n\nimport { MessageBus } from \"../bus/queue.js\";\nimport { LineChannel, verifyLineSignature } from \"./line.js\";\n\n// Construct a minimal LineChannel for testing private methods\nconst bus = new MessageBus();\nconst channel = new LineChannel(\n {\n allowFrom: [],\n channelAccessToken: \"test\",\n channelSecret: \"test\",\n enabled: false,\n },\n bus\n);\n\n// Access private methods via any\nconst ch = channel as unknown as {\n parseMessage(text: string): {\n type: string;\n text?: string;\n altText?: string;\n contents?: unknown;\n }[];\n findJsonEnd(str: string): number;\n extractAltText(contents: Record<string, unknown>): string;\n returnFlexMessage(\n message: string\n ): { type: string; altText?: string; contents?: unknown }[];\n};\n\n// ---------------------------------------------------------------------------\n// findJsonEnd\n// ---------------------------------------------------------------------------\ndescribe(\"findJsonEnd\", () => {\n it(\"finds end of simple object\", () => {\n expect(ch.findJsonEnd('{\"a\":1}')).toBe(7);\n });\n\n it(\"finds end of nested object\", () => {\n const json = '{\"a\":{\"b\":{\"c\":1}}}';\n expect(ch.findJsonEnd(json)).toBe(json.length);\n });\n\n it(\"finds end with trailing text\", () => {\n const str = '{\"type\":\"bubble\"} some trailing text';\n expect(ch.findJsonEnd(str)).toBe(17);\n });\n\n it(\"handles strings with escaped quotes\", () => {\n const json = '{\"text\":\"say \\\\\"hello\\\\\"\"}';\n expect(ch.findJsonEnd(json)).toBe(json.length);\n });\n\n it(\"handles strings with braces inside\", () => {\n const json = '{\"text\":\"{ not a real brace }\"}';\n expect(ch.findJsonEnd(json)).toBe(json.length);\n });\n\n it(\"handles escaped backslash before quote\", () => {\n // The string value is: path\\\\ (backslash is escaped, quote is not)\n const json = '{\"path\":\"C:\\\\\\\\\"}';\n expect(ch.findJsonEnd(json)).toBe(json.length);\n });\n\n it(\"returns -1 for non-object string\", () => {\n expect(ch.findJsonEnd(\"hello\")).toBe(-1);\n });\n\n it(\"returns -1 for unclosed object\", () => {\n expect(ch.findJsonEnd('{\"a\":1')).toBe(-1);\n });\n\n it(\"handles empty object\", () => {\n expect(ch.findJsonEnd(\"{}\")).toBe(2);\n });\n\n it(\"handles arrays inside objects\", () => {\n const json = '{\"items\":[1,2,3]}';\n expect(ch.findJsonEnd(json)).toBe(json.length);\n });\n});\n\n// ---------------------------------------------------------------------------\n// extractAltText\n// ---------------------------------------------------------------------------\ndescribe(\"extractAltText\", () => {\n it(\"extracts text from a simple bubble\", () => {\n const result = ch.extractAltText({\n body: { type: \"box\", contents: [{ type: \"text\", text: \"Hello World\" }] },\n type: \"bubble\",\n });\n expect(result).toBe(\"Hello World\");\n });\n\n it(\"extracts title when present\", () => {\n const result = ch.extractAltText({\n body: { type: \"box\", contents: [] },\n title: \"Card Title\",\n type: \"bubble\",\n });\n expect(result).toBe(\"Card Title\");\n });\n\n it(\"falls back to 'Flex Message' when no text found\", () => {\n const result = ch.extractAltText({ type: \"bubble\" });\n expect(result).toBe(\"Flex Message\");\n });\n\n it(\"truncates long text to 100 chars\", () => {\n const longText = \"x\".repeat(200);\n const result = ch.extractAltText({\n body: { type: \"box\", contents: [{ type: \"text\", text: longText }] },\n type: \"bubble\",\n });\n expect(result).toHaveLength(100);\n });\n});\n\n// ---------------------------------------------------------------------------\n// parseMessage\n// ---------------------------------------------------------------------------\ndescribe(\"parseMessage\", () => {\n it(\"returns plain text for normal messages\", () => {\n const result = ch.parseMessage(\"Hello, world!\");\n expect(result).toHaveLength(1);\n expect(result[0].type).toBe(\"text\");\n expect(result[0].text).toBe(\"Hello, world!\");\n });\n\n it(\"returns (empty) for empty string\", () => {\n const result = ch.parseMessage(\"\");\n expect(result).toHaveLength(1);\n expect(result[0].text).toBe(\"(empty)\");\n });\n\n it(\"parses pure JSON flex message\", () => {\n const flex = JSON.stringify({\n body: {\n type: \"box\",\n layout: \"vertical\",\n contents: [{ type: \"text\", text: \"Test\" }],\n },\n type: \"bubble\",\n });\n const result = ch.parseMessage(flex);\n expect(result).toHaveLength(1);\n expect(result[0].type).toBe(\"flex\");\n expect(result[0].altText).toBe(\"Test\");\n });\n\n it(\"handles JSON with trailing text\", () => {\n const flex = JSON.stringify({\n body: {\n type: \"box\",\n layout: \"vertical\",\n contents: [{ type: \"text\", text: \"Card\" }],\n },\n type: \"bubble\",\n });\n const input = flex + \"\\n\\nHere is some extra text!\";\n const result = ch.parseMessage(input);\n expect(result).toHaveLength(2);\n expect(result[0].type).toBe(\"flex\");\n expect(result[1].type).toBe(\"text\");\n expect(result[1].text).toBe(\"Here is some extra text!\");\n });\n\n it(\"handles JSON with Japanese trailing text\", () => {\n const flex = JSON.stringify({\n body: {\n type: \"box\",\n layout: \"vertical\",\n contents: [{ type: \"text\", text: \"運勢\" }],\n },\n type: \"bubble\",\n });\n const input = flex + \"\\n\\n記録できた!今日もがんばろう。\";\n const result = ch.parseMessage(input);\n expect(result).toHaveLength(2);\n expect(result[0].type).toBe(\"flex\");\n expect(result[1].type).toBe(\"text\");\n expect(result[1].text).toBe(\"記録できた!今日もがんばろう。\");\n });\n\n it(\"handles whitespace around JSON\", () => {\n const flex = JSON.stringify({\n body: { type: \"box\", contents: [{ type: \"text\", text: \"OK\" }] },\n type: \"bubble\",\n });\n const result = ch.parseMessage(\" \" + flex + \" \");\n expect(result).toHaveLength(1);\n expect(result[0].type).toBe(\"flex\");\n });\n\n it(\"falls back to text for invalid JSON starting with {\", () => {\n const result = ch.parseMessage(\"{not valid json at all}\");\n expect(result).toHaveLength(1);\n expect(result[0].type).toBe(\"text\");\n expect(result[0].text).toBe(\"{not valid json at all}\");\n });\n\n it(\"falls back to text for non-JSON curly brace text\", () => {\n const result = ch.parseMessage(\"{incomplete json\");\n expect(result).toHaveLength(1);\n expect(result[0].type).toBe(\"text\");\n });\n\n it(\"splits flex JSON with prefix and suffix text\", () => {\n const input = `完成!こんな感じ:{\"type\":\"bubble\",\"body\":{\"type\":\"box\",\"layout\":\"vertical\",\"contents\":[{\"type\":\"box\",\"layout\":\"vertical\",\"contents\":[{\"type\": \"text\",\"text\":\"🌱 水やり記録\",\"size\": \"xl\" ,\"weight\":\"bold\"},{\"type\": \"text\",\"text\":\"前回: 今日\\\\n植物たち元気?\", \"color\":\"#666666\",\"margin\":\"md\"}]},{\"type\": \"separator\",\"margin\":\"lg\"},{\"type\": \"box\",\"layout\":\"horizontal\",\"margin\":\"md\",\"spacing\":\"md\",\"contents\":[{\"type\": \"button\",\"style\": \"primary\",\"color\": \"#4CAF50\",\"action\":{\"type\": \"message\",\"label\": \"水やりした!\", \"text\": \"水やりした\"}},{\"type\": \"button\",\"style\": \"secondary\",\"action\":{\"type\": \"message\",\"label\": \"最後にいつ?\", \"text\": \"水やり確認\"}}]}]}}\\n\\n記録できた!今日水やりしたことになってる。緑色のボタンがカワイイでしょ。`;\n const result = ch.parseMessage(input);\n expect(result).toHaveLength(3);\n expect(result[0].type).toBe(\"text\");\n expect(result[0].text).toBe(\"完成!こんな感じ:\");\n expect(result[1].type).toBe(\"flex\");\n expect(result[1].altText).toBe(\"🌱 水やり記録\");\n expect(result[2].type).toBe(\"text\");\n expect(result[2].text).toBe(\n \"記録できた!今日水やりしたことになってる。緑色のボタンがカワイイでしょ。\"\n );\n });\n\n it(\"splits real-world flex JSON with trailing Japanese text\", () => {\n const input = `{\"type\":\"bubble\",\"body\":{\"type\":\"box\",\"layout\":\"vertical\",\"contents\":[{\"type\":\"box\",\"layout\":\"vertical\",\"contents\":[{\"type\": \"text\",\"text\":\"🌱 水やり記録\",\"size\": \"xl\" ,\"weight\":\"bold\"},{\"type\": \"text\",\"text\":\"前回: 今日\\\\n植物たち元気?\", \"color\":\"#666666\",\"margin\":\"md\"}]},{\"type\": \"separator\",\"margin\":\"lg\"},{\"type\": \"box\",\"layout\":\"horizontal\",\"margin\":\"md\",\"spacing\":\"md\",\"contents\":[{\"type\": \"button\",\"style\": \"primary\",\"color\": \"#4CAF50\",\"action\":{\"type\": \"message\",\"label\": \"水やりした!\", \"text\": \"水やりした\"}},{\"type\": \"button\",\"style\": \"secondary\",\"action\":{\"type\": \"message\",\"label\": \"最後にいつ?\", \"text\": \"水やり確認\"}}]}]}}\\n\\n記録できた!今日水やりしたことになってる。緑色のボタンがカワイイでしょ。`;\n const result = ch.parseMessage(input);\n expect(result).toHaveLength(2);\n expect(result[0].type).toBe(\"flex\");\n expect(result[1].type).toBe(\"text\");\n expect(result[1].text).toBe(\n \"記録できた!今日水やりしたことになってる。緑色のボタンがカワイイでしょ。\"\n );\n });\n});\n\n// ---------------------------------------------------------------------------\n// verifyLineSignature (exported function)\n// ---------------------------------------------------------------------------\ndescribe(\"verifyLineSignature works\", () => {\n it(\"returns true for valid signature\", () => {\n const secret = \"test-secret\";\n const body = '{\"events\":[]}';\n // Compute the expected signature\n const expected = createHmac(\"sha256\", secret).update(body).digest(\"base64\");\n expect(verifyLineSignature(secret, body, expected)).toBeTruthy();\n });\n\n it(\"returns false for invalid signature\", () => {\n expect(verifyLineSignature(\"secret\", \"body\", \"bad-sig\")).toBeFalsy();\n });\n\n it(\"returns false for empty signature\", () => {\n expect(verifyLineSignature(\"secret\", \"body\", \"\")).toBeFalsy();\n });\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.mts","names":[],"sources":["../../src/channels/manager.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"manager.d.mts","names":[],"sources":["../../src/channels/manager.ts"],"mappings":";;;;;;;AASA;cAAa,cAAA;EAAA,QACH,MAAA;EAAA,QACA,GAAA;EAAA,SACC,QAAA,EAAQ,GAAA,SAAA,WAAA;EAAA,QACT,aAAA;cAEI,MAAA,EAAQ,MAAA,EAAQ,GAAA,EAAK,UAAA;EAAA,QAKnB,YAAA;EAuCI;EALZ,IAAA,CAAA,GAAQ,OAAA;EAuEY;EAlEpB,QAAA,CAAA,GAAY,OAAA;EAsEC;EA5Cb,OAAA,CAAA,GAAW,OAAA;EAAA,QAkBH,gBAAA;EAsBd,UAAA,CAAW,IAAA,WAAe,WAAA;EAI1B,SAAA,CAAA,GAAa,MAAA;IAAiB,OAAA;IAAkB,OAAA;EAAA;EAAA,IAQ5C,eAAA,CAAA;AAAA"}
|
|
@@ -19,16 +19,16 @@ var ChannelManager = class {
|
|
|
19
19
|
const channel = new TelegramChannel(this.config.channels.telegram, this.bus);
|
|
20
20
|
this.channels.set("telegram", channel);
|
|
21
21
|
console.log("Telegram channel enabled");
|
|
22
|
-
} catch (
|
|
23
|
-
console.warn("Telegram channel not available:",
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.warn("Telegram channel not available:", error);
|
|
24
24
|
}
|
|
25
25
|
if (this.config.channels.line.enabled) try {
|
|
26
26
|
const { LineChannel } = await import("./line.mjs");
|
|
27
27
|
const channel = new LineChannel(this.config.channels.line, this.bus, getConfigWorkspacePath(this.config));
|
|
28
28
|
this.channels.set("line", channel);
|
|
29
29
|
console.log("LINE channel enabled (webhook mode)");
|
|
30
|
-
} catch (
|
|
31
|
-
console.warn("LINE channel not available:",
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.warn("LINE channel not available:", error);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
/** Initialize channel instances (non-blocking). Call before startAll(). */
|
|
@@ -61,8 +61,8 @@ var ChannelManager = class {
|
|
|
61
61
|
for (const [name, channel] of this.channels) try {
|
|
62
62
|
await channel.stop();
|
|
63
63
|
console.log(`Stopped ${name} channel`);
|
|
64
|
-
} catch (
|
|
65
|
-
console.error(`Error stopping ${name}:`,
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(`Error stopping ${name}:`, error);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
async dispatchOutbound(signal) {
|
|
@@ -72,8 +72,8 @@ var ChannelManager = class {
|
|
|
72
72
|
const channel = this.channels.get(msg.channel);
|
|
73
73
|
if (channel) try {
|
|
74
74
|
await channel.send(msg);
|
|
75
|
-
} catch (
|
|
76
|
-
console.error(`Error sending to ${msg.channel}:`,
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(`Error sending to ${msg.channel}:`, error);
|
|
77
77
|
}
|
|
78
78
|
else console.warn(`Unknown channel: ${msg.channel}`);
|
|
79
79
|
} catch {}
|
|
@@ -90,7 +90,7 @@ var ChannelManager = class {
|
|
|
90
90
|
return status;
|
|
91
91
|
}
|
|
92
92
|
get enabledChannels() {
|
|
93
|
-
return
|
|
93
|
+
return [...this.channels.keys()];
|
|
94
94
|
}
|
|
95
95
|
};
|
|
96
96
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.mjs","names":[],"sources":["../../src/channels/manager.ts"],"sourcesContent":["import type { MessageBus } from \"../bus/queue.js\";\nimport type {
|
|
1
|
+
{"version":3,"file":"manager.mjs","names":[],"sources":["../../src/channels/manager.ts"],"sourcesContent":["import type { MessageBus } from \"../bus/queue.js\";\nimport type { Config } from \"../config/schema.js\";\nimport type { BaseChannel } from \"./base.js\";\n\nimport { getConfigWorkspacePath } from \"../config/schema.js\";\n\n/**\n * Manages chat channels and coordinates message routing.\n */\nexport class ChannelManager {\n private config: Config;\n private bus: MessageBus;\n readonly channels = new Map<string, BaseChannel>();\n private dispatchAbort: AbortController | null = null;\n\n constructor(config: Config, bus: MessageBus) {\n this.config = config;\n this.bus = bus;\n }\n\n private async initChannels(): Promise<void> {\n // Telegram channel\n if (this.config.channels.telegram.enabled) {\n try {\n const { TelegramChannel } = await import(\"./telegram.js\");\n const channel = new TelegramChannel(\n this.config.channels.telegram,\n this.bus\n );\n this.channels.set(\"telegram\", channel);\n console.log(\"Telegram channel enabled\");\n } catch (error) {\n console.warn(\"Telegram channel not available:\", error);\n }\n }\n\n // LINE channel\n if (this.config.channels.line.enabled) {\n try {\n const { LineChannel } = await import(\"./line.js\");\n const channel = new LineChannel(\n this.config.channels.line,\n this.bus,\n getConfigWorkspacePath(this.config)\n );\n this.channels.set(\"line\", channel);\n console.log(\"LINE channel enabled (webhook mode)\");\n } catch (error) {\n console.warn(\"LINE channel not available:\", error);\n }\n }\n }\n\n /** Initialize channel instances (non-blocking). Call before startAll(). */\n async init(): Promise<void> {\n await this.initChannels();\n }\n\n /** Start all channels and the outbound dispatcher (blocks until stopped). */\n async startAll(): Promise<void> {\n // Init if not already done\n if (this.channels.size === 0) {\n await this.initChannels();\n }\n\n if (this.channels.size === 0) {\n console.warn(\"No channels enabled\");\n return;\n }\n\n // Start outbound dispatcher\n this.dispatchAbort = new AbortController();\n const dispatchPromise = this.dispatchOutbound(this.dispatchAbort.signal);\n\n // Start all channels\n const channelPromises: Promise<void>[] = [];\n for (const [name, channel] of this.channels) {\n console.log(`Starting ${name} channel...`);\n channelPromises.push(channel.start());\n }\n\n await Promise.all([dispatchPromise, ...channelPromises]);\n }\n\n /** Stop all channels and the dispatcher. */\n async stopAll(): Promise<void> {\n console.log(\"Stopping all channels...\");\n\n if (this.dispatchAbort) {\n this.dispatchAbort.abort();\n this.dispatchAbort = null;\n }\n\n for (const [name, channel] of this.channels) {\n try {\n await channel.stop();\n console.log(`Stopped ${name} channel`);\n } catch (error) {\n console.error(`Error stopping ${name}:`, error);\n }\n }\n }\n\n private async dispatchOutbound(signal: AbortSignal): Promise<void> {\n console.log(\"Outbound dispatcher started\");\n\n while (!signal.aborted) {\n try {\n const msg = await this.bus.consumeOutboundTimeout(1000);\n const channel = this.channels.get(msg.channel);\n if (channel) {\n try {\n await channel.send(msg);\n } catch (error) {\n console.error(`Error sending to ${msg.channel}:`, error);\n }\n } else {\n console.warn(`Unknown channel: ${msg.channel}`);\n }\n } catch {\n // timeout, continue\n }\n }\n }\n\n getChannel(name: string): BaseChannel | undefined {\n return this.channels.get(name);\n }\n\n getStatus(): Record<string, { enabled: boolean; running: boolean }> {\n const status: Record<string, { enabled: boolean; running: boolean }> = {};\n for (const [name, channel] of this.channels) {\n status[name] = { enabled: true, running: channel.isRunning };\n }\n return status;\n }\n\n get enabledChannels(): string[] {\n return [...this.channels.keys()];\n }\n}\n"],"mappings":";;;;;;AASA,IAAa,iBAAb,MAA4B;CAC1B,AAAQ;CACR,AAAQ;CACR,AAAS,2BAAW,IAAI,KAA0B;CAClD,AAAQ,gBAAwC;CAEhD,YAAY,QAAgB,KAAiB;AAC3C,OAAK,SAAS;AACd,OAAK,MAAM;;CAGb,MAAc,eAA8B;AAE1C,MAAI,KAAK,OAAO,SAAS,SAAS,QAChC,KAAI;GACF,MAAM,EAAE,oBAAoB,MAAM,OAAO;GACzC,MAAM,UAAU,IAAI,gBAClB,KAAK,OAAO,SAAS,UACrB,KAAK,IACN;AACD,QAAK,SAAS,IAAI,YAAY,QAAQ;AACtC,WAAQ,IAAI,2BAA2B;WAChC,OAAO;AACd,WAAQ,KAAK,mCAAmC,MAAM;;AAK1D,MAAI,KAAK,OAAO,SAAS,KAAK,QAC5B,KAAI;GACF,MAAM,EAAE,gBAAgB,MAAM,OAAO;GACrC,MAAM,UAAU,IAAI,YAClB,KAAK,OAAO,SAAS,MACrB,KAAK,KACL,uBAAuB,KAAK,OAAO,CACpC;AACD,QAAK,SAAS,IAAI,QAAQ,QAAQ;AAClC,WAAQ,IAAI,sCAAsC;WAC3C,OAAO;AACd,WAAQ,KAAK,+BAA+B,MAAM;;;;CAMxD,MAAM,OAAsB;AAC1B,QAAM,KAAK,cAAc;;;CAI3B,MAAM,WAA0B;AAE9B,MAAI,KAAK,SAAS,SAAS,EACzB,OAAM,KAAK,cAAc;AAG3B,MAAI,KAAK,SAAS,SAAS,GAAG;AAC5B,WAAQ,KAAK,sBAAsB;AACnC;;AAIF,OAAK,gBAAgB,IAAI,iBAAiB;EAC1C,MAAM,kBAAkB,KAAK,iBAAiB,KAAK,cAAc,OAAO;EAGxE,MAAM,kBAAmC,EAAE;AAC3C,OAAK,MAAM,CAAC,MAAM,YAAY,KAAK,UAAU;AAC3C,WAAQ,IAAI,YAAY,KAAK,aAAa;AAC1C,mBAAgB,KAAK,QAAQ,OAAO,CAAC;;AAGvC,QAAM,QAAQ,IAAI,CAAC,iBAAiB,GAAG,gBAAgB,CAAC;;;CAI1D,MAAM,UAAyB;AAC7B,UAAQ,IAAI,2BAA2B;AAEvC,MAAI,KAAK,eAAe;AACtB,QAAK,cAAc,OAAO;AAC1B,QAAK,gBAAgB;;AAGvB,OAAK,MAAM,CAAC,MAAM,YAAY,KAAK,SACjC,KAAI;AACF,SAAM,QAAQ,MAAM;AACpB,WAAQ,IAAI,WAAW,KAAK,UAAU;WAC/B,OAAO;AACd,WAAQ,MAAM,kBAAkB,KAAK,IAAI,MAAM;;;CAKrD,MAAc,iBAAiB,QAAoC;AACjE,UAAQ,IAAI,8BAA8B;AAE1C,SAAO,CAAC,OAAO,QACb,KAAI;GACF,MAAM,MAAM,MAAM,KAAK,IAAI,uBAAuB,IAAK;GACvD,MAAM,UAAU,KAAK,SAAS,IAAI,IAAI,QAAQ;AAC9C,OAAI,QACF,KAAI;AACF,UAAM,QAAQ,KAAK,IAAI;YAChB,OAAO;AACd,YAAQ,MAAM,oBAAoB,IAAI,QAAQ,IAAI,MAAM;;OAG1D,SAAQ,KAAK,oBAAoB,IAAI,UAAU;UAE3C;;CAMZ,WAAW,MAAuC;AAChD,SAAO,KAAK,SAAS,IAAI,KAAK;;CAGhC,YAAoE;EAClE,MAAM,SAAiE,EAAE;AACzE,OAAK,MAAM,CAAC,MAAM,YAAY,KAAK,SACjC,QAAO,QAAQ;GAAE,SAAS;GAAM,SAAS,QAAQ;GAAW;AAE9D,SAAO;;CAGT,IAAI,kBAA4B;AAC9B,SAAO,CAAC,GAAG,KAAK,SAAS,MAAM,CAAC"}
|
|
@@ -9,31 +9,31 @@ import { Bot } from "grammy";
|
|
|
9
9
|
function markdownToTelegramHtml(text) {
|
|
10
10
|
if (!text) return "";
|
|
11
11
|
const codeBlocks = [];
|
|
12
|
-
let result = text.
|
|
12
|
+
let result = text.replaceAll(/```[\w]*\n?([\s\S]*?)```/g, (_m, code) => {
|
|
13
13
|
codeBlocks.push(code);
|
|
14
|
-
return `\
|
|
14
|
+
return `\u0000CB${codeBlocks.length - 1}\u0000`;
|
|
15
15
|
});
|
|
16
16
|
const inlineCodes = [];
|
|
17
|
-
result = result.
|
|
17
|
+
result = result.replaceAll(/`([^`]+)`/g, (_m, code) => {
|
|
18
18
|
inlineCodes.push(code);
|
|
19
|
-
return `\
|
|
19
|
+
return `\u0000IC${inlineCodes.length - 1}\u0000`;
|
|
20
20
|
});
|
|
21
|
-
result = result.
|
|
22
|
-
result = result.
|
|
23
|
-
result = result.
|
|
24
|
-
result = result.
|
|
25
|
-
result = result.
|
|
26
|
-
result = result.
|
|
27
|
-
result = result.
|
|
28
|
-
result = result.
|
|
29
|
-
result = result.
|
|
30
|
-
for (let i = 0; i < inlineCodes.length; i
|
|
31
|
-
const escaped = inlineCodes[i].
|
|
32
|
-
result = result.replace(`\
|
|
21
|
+
result = result.replaceAll(/^#{1,6}\s+(.+)$/gm, "$1");
|
|
22
|
+
result = result.replaceAll(/^>\s*(.*)$/gm, "$1");
|
|
23
|
+
result = result.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
24
|
+
result = result.replaceAll(/\[([^\]]+)\]\(([^)]+)\)/g, "<a href=\"$2\">$1</a>");
|
|
25
|
+
result = result.replaceAll(/\*\*(.+?)\*\*/g, "<b>$1</b>");
|
|
26
|
+
result = result.replaceAll(/__(.+?)__/g, "<b>$1</b>");
|
|
27
|
+
result = result.replaceAll(/(?<![a-zA-Z0-9])_([^_]+)_(?![a-zA-Z0-9])/g, "<i>$1</i>");
|
|
28
|
+
result = result.replaceAll(/~~(.+?)~~/g, "<s>$1</s>");
|
|
29
|
+
result = result.replaceAll(/^[-*]\s+/gm, "• ");
|
|
30
|
+
for (let i = 0; i < inlineCodes.length; i += 1) {
|
|
31
|
+
const escaped = inlineCodes[i].replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
32
|
+
result = result.replace(`\u0000IC${i}\u0000`, `<code>${escaped}</code>`);
|
|
33
33
|
}
|
|
34
|
-
for (let i = 0; i < codeBlocks.length; i
|
|
35
|
-
const escaped = codeBlocks[i].
|
|
36
|
-
result = result.replace(`\
|
|
34
|
+
for (let i = 0; i < codeBlocks.length; i += 1) {
|
|
35
|
+
const escaped = codeBlocks[i].replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
36
|
+
result = result.replace(`\u0000CB${i}\u0000`, `<pre><code>${escaped}</code></pre>`);
|
|
37
37
|
}
|
|
38
38
|
return result;
|
|
39
39
|
}
|
|
@@ -72,20 +72,20 @@ var TelegramChannel = class extends BaseChannel {
|
|
|
72
72
|
const me = await this.bot.api.getMe();
|
|
73
73
|
console.log(`Telegram bot @${me.username} connected`);
|
|
74
74
|
const maxRetries = 3;
|
|
75
|
-
for (let attempt = 1; attempt <= maxRetries; attempt
|
|
75
|
+
for (let attempt = 1; attempt <= maxRetries; attempt += 1) try {
|
|
76
76
|
await this.bot.start({
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
allowed_updates: ["message"],
|
|
78
|
+
drop_pending_updates: true
|
|
79
79
|
});
|
|
80
80
|
return;
|
|
81
|
-
} catch (
|
|
82
|
-
if (
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error instanceof Error && error.message.includes("409") && attempt < maxRetries && this._running) {
|
|
83
83
|
const delay = attempt * 5;
|
|
84
84
|
console.warn(`Telegram 409 conflict (attempt ${attempt}/${maxRetries}), retrying in ${delay}s...`);
|
|
85
85
|
await new Promise((r) => setTimeout(r, delay * 1e3));
|
|
86
86
|
continue;
|
|
87
87
|
}
|
|
88
|
-
throw
|
|
88
|
+
throw error;
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
async stop() {
|
|
@@ -103,7 +103,7 @@ var TelegramChannel = class extends BaseChannel {
|
|
|
103
103
|
}
|
|
104
104
|
try {
|
|
105
105
|
const chatId = Number(msg.chatId);
|
|
106
|
-
if (isNaN(chatId)) {
|
|
106
|
+
if (Number.isNaN(chatId)) {
|
|
107
107
|
console.error(`Invalid chat_id: ${msg.chatId}`);
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
@@ -114,13 +114,13 @@ var TelegramChannel = class extends BaseChannel {
|
|
|
114
114
|
console.warn("HTML parse failed, falling back to plain text");
|
|
115
115
|
await this.bot.api.sendMessage(chatId, msg.content);
|
|
116
116
|
}
|
|
117
|
-
} catch (
|
|
118
|
-
console.error("Error sending Telegram message:",
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error("Error sending Telegram message:", error);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
async onMessage(ctx) {
|
|
122
122
|
const user = ctx.from;
|
|
123
|
-
const message = ctx
|
|
123
|
+
const { message } = ctx;
|
|
124
124
|
if (!user || !message) return;
|
|
125
125
|
const chatId = message.chat.id;
|
|
126
126
|
let senderId = String(user.id);
|
|
@@ -130,20 +130,19 @@ var TelegramChannel = class extends BaseChannel {
|
|
|
130
130
|
if (message.text) contentParts.push(message.text);
|
|
131
131
|
if (message.caption) contentParts.push(message.caption);
|
|
132
132
|
if (message.photo && message.photo.length > 0) {
|
|
133
|
-
const photo = message.photo
|
|
133
|
+
const photo = message.photo.at(-1);
|
|
134
134
|
try {
|
|
135
|
-
const file = await ctx.api.getFile(photo
|
|
135
|
+
const file = await ctx.api.getFile(photo?.file_id ?? "");
|
|
136
136
|
const mediaDir = join(homedir(), ".nanobot", "media");
|
|
137
137
|
if (!existsSync(mediaDir)) mkdirSync(mediaDir, { recursive: true });
|
|
138
138
|
contentParts.push(`[image: telegram file ${file.file_id}]`);
|
|
139
|
-
} catch
|
|
139
|
+
} catch {
|
|
140
140
|
contentParts.push("[image: download failed]");
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
if (message.document) contentParts.push(`[file: ${message.document.file_name ?? message.document.file_id}]`);
|
|
144
144
|
const content = contentParts.length > 0 ? contentParts.join("\n") : "[empty message]";
|
|
145
145
|
await this.handleMessage({
|
|
146
|
-
senderId,
|
|
147
146
|
chatId: String(chatId),
|
|
148
147
|
content,
|
|
149
148
|
media: mediaPaths,
|
|
@@ -153,7 +152,8 @@ var TelegramChannel = class extends BaseChannel {
|
|
|
153
152
|
username: user.username,
|
|
154
153
|
firstName: user.first_name,
|
|
155
154
|
isGroup: message.chat.type !== "private"
|
|
156
|
-
}
|
|
155
|
+
},
|
|
156
|
+
senderId
|
|
157
157
|
});
|
|
158
158
|
}
|
|
159
159
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telegram.mjs","names":[],"sources":["../../src/channels/telegram.ts"],"sourcesContent":["import { Bot, type Context } from \"grammy\";\nimport { mkdirSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { OutboundMessage } from \"../bus/events.js\";\nimport type { MessageBus } from \"../bus/queue.js\";\nimport { BaseChannel } from \"./base.js\";\nimport type { TelegramConfig } from \"../config/schema.js\";\n\n/** Convert markdown to Telegram-safe HTML. */\nfunction markdownToTelegramHtml(text: string): string {\n if (!text) return \"\";\n\n // 1. Extract and protect code blocks\n const codeBlocks: string[] = [];\n let result = text.replace(/```[\\w]*\\n?([\\s\\S]*?)```/g, (_m, code) => {\n codeBlocks.push(code);\n return `\\x00CB${codeBlocks.length - 1}\\x00`;\n });\n\n // 2. Extract and protect inline code\n const inlineCodes: string[] = [];\n result = result.replace(/`([^`]+)`/g, (_m, code) => {\n inlineCodes.push(code);\n return `\\x00IC${inlineCodes.length - 1}\\x00`;\n });\n\n // 3. Headers -> plain text\n result = result.replace(/^#{1,6}\\s+(.+)$/gm, \"$1\");\n\n // 4. Blockquotes -> plain text\n result = result.replace(/^>\\s*(.*)$/gm, \"$1\");\n\n // 5. Escape HTML\n result = result\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n\n // 6. Links\n result = result.replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n '<a href=\"$2\">$1</a>',\n );\n\n // 7. Bold\n result = result.replace(/\\*\\*(.+?)\\*\\*/g, \"<b>$1</b>\");\n result = result.replace(/__(.+?)__/g, \"<b>$1</b>\");\n\n // 8. Italic\n result = result.replace(\n /(?<![a-zA-Z0-9])_([^_]+)_(?![a-zA-Z0-9])/g,\n \"<i>$1</i>\",\n );\n\n // 9. Strikethrough\n result = result.replace(/~~(.+?)~~/g, \"<s>$1</s>\");\n\n // 10. Bullet lists\n result = result.replace(/^[-*]\\s+/gm, \"\\u2022 \");\n\n // 11. Restore inline code\n for (let i = 0; i < inlineCodes.length; i++) {\n const escaped = inlineCodes[i]\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n result = result.replace(\n `\\x00IC${i}\\x00`,\n `<code>${escaped}</code>`,\n );\n }\n\n // 12. Restore code blocks\n for (let i = 0; i < codeBlocks.length; i++) {\n const escaped = codeBlocks[i]\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n result = result.replace(\n `\\x00CB${i}\\x00`,\n `<pre><code>${escaped}</code></pre>`,\n );\n }\n\n return result;\n}\n\n/**\n * Telegram channel using grammY (long polling).\n */\nexport class TelegramChannel extends BaseChannel {\n readonly name = \"telegram\";\n private bot: Bot | null = null;\n private telegramConfig: TelegramConfig;\n\n constructor(config: TelegramConfig, bus: MessageBus) {\n super(config, bus);\n this.telegramConfig = config;\n }\n\n async start(): Promise<void> {\n if (!this.telegramConfig.token) {\n console.error(\"Telegram bot token not configured\");\n return;\n }\n\n this._running = true;\n this.bot = new Bot(this.telegramConfig.token);\n\n // Handle /start command\n this.bot.command(\"start\", async (ctx) => {\n const user = ctx.from;\n await ctx.reply(\n `Hi ${user?.first_name ?? \"there\"}! I'm nanobot.\\n\\nSend me a message and I'll respond!`,\n );\n });\n\n // Handle text messages\n this.bot.on(\"message:text\", async (ctx) => {\n await this.onMessage(ctx);\n });\n\n // Handle photos\n this.bot.on(\"message:photo\", async (ctx) => {\n await this.onMessage(ctx);\n });\n\n // Handle documents\n this.bot.on(\"message:document\", async (ctx) => {\n await this.onMessage(ctx);\n });\n\n console.log(\"Starting Telegram bot (polling mode)...\");\n\n const me = await this.bot.api.getMe();\n console.log(`Telegram bot @${me.username} connected`);\n\n // Start polling with retry — handles 409 Conflict when a stale\n // instance is still holding the long-poll connection.\n const maxRetries = 3;\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n try {\n await this.bot.start({\n drop_pending_updates: true,\n allowed_updates: [\"message\"],\n });\n return; // bot.start() blocks until stopped, so if we get here it was a clean stop\n } catch (err) {\n const is409 =\n err instanceof Error && err.message.includes(\"409\");\n if (is409 && attempt < maxRetries && this._running) {\n const delay = attempt * 5;\n console.warn(\n `Telegram 409 conflict (attempt ${attempt}/${maxRetries}), retrying in ${delay}s...`,\n );\n await new Promise((r) => setTimeout(r, delay * 1000));\n continue;\n }\n throw err;\n }\n }\n }\n\n async stop(): Promise<void> {\n this._running = false;\n if (this.bot) {\n console.log(\"Stopping Telegram bot...\");\n await this.bot.stop();\n this.bot = null;\n }\n }\n\n async send(msg: OutboundMessage): Promise<void> {\n if (!this.bot) {\n console.warn(\"Telegram bot not running\");\n return;\n }\n\n try {\n const chatId = Number(msg.chatId);\n if (isNaN(chatId)) {\n console.error(`Invalid chat_id: ${msg.chatId}`);\n return;\n }\n\n const htmlContent = markdownToTelegramHtml(msg.content);\n try {\n await this.bot.api.sendMessage(chatId, htmlContent, {\n parse_mode: \"HTML\",\n });\n } catch {\n // Fallback to plain text\n console.warn(\"HTML parse failed, falling back to plain text\");\n await this.bot.api.sendMessage(chatId, msg.content);\n }\n } catch (err) {\n console.error(\"Error sending Telegram message:\", err);\n }\n }\n\n private async onMessage(ctx: Context): Promise<void> {\n const user = ctx.from;\n const message = ctx.message;\n if (!user || !message) return;\n\n const chatId = message.chat.id;\n let senderId = String(user.id);\n if (user.username) {\n senderId = `${senderId}|${user.username}`;\n }\n\n const contentParts: string[] = [];\n const mediaPaths: string[] = [];\n\n // Text\n if (message.text) contentParts.push(message.text);\n if (message.caption) contentParts.push(message.caption);\n\n // Photos\n if (message.photo && message.photo.length > 0) {\n const photo = message.photo[message.photo.length - 1];\n try {\n const file = await ctx.api.getFile(photo.file_id);\n const mediaDir = join(homedir(), \".nanobot\", \"media\");\n if (!existsSync(mediaDir)) mkdirSync(mediaDir, { recursive: true });\n // Note: grammy doesn't have a built-in download_to_drive.\n // In WebContainer context, we just note the file_id.\n contentParts.push(`[image: telegram file ${file.file_id}]`);\n } catch (err) {\n contentParts.push(\"[image: download failed]\");\n }\n }\n\n // Documents\n if (message.document) {\n contentParts.push(\n `[file: ${message.document.file_name ?? message.document.file_id}]`,\n );\n }\n\n const content =\n contentParts.length > 0 ? contentParts.join(\"\\n\") : \"[empty message]\";\n\n await this.handleMessage({\n senderId,\n chatId: String(chatId),\n content,\n media: mediaPaths,\n metadata: {\n messageId: message.message_id,\n userId: user.id,\n username: user.username,\n firstName: user.first_name,\n isGroup: message.chat.type !== \"private\",\n },\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"telegram.mjs","names":[],"sources":["../../src/channels/telegram.ts"],"sourcesContent":["import type { Context } from \"grammy\";\n\nimport { Bot } from \"grammy\";\nimport { mkdirSync, existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport type { OutboundMessage } from \"../bus/events.js\";\nimport type { MessageBus } from \"../bus/queue.js\";\nimport type { TelegramConfig } from \"../config/schema.js\";\n\nimport { BaseChannel } from \"./base.js\";\n\n/** Convert markdown to Telegram-safe HTML. */\nfunction markdownToTelegramHtml(text: string): string {\n if (!text) {\n return \"\";\n }\n\n // 1. Extract and protect code blocks\n const codeBlocks: string[] = [];\n let result = text.replaceAll(/```[\\w]*\\n?([\\s\\S]*?)```/g, (_m, code) => {\n codeBlocks.push(code);\n return `\\u0000CB${codeBlocks.length - 1}\\u0000`;\n });\n\n // 2. Extract and protect inline code\n const inlineCodes: string[] = [];\n result = result.replaceAll(/`([^`]+)`/g, (_m, code) => {\n inlineCodes.push(code);\n return `\\u0000IC${inlineCodes.length - 1}\\u0000`;\n });\n\n // 3. Headers -> plain text\n result = result.replaceAll(/^#{1,6}\\s+(.+)$/gm, \"$1\");\n\n // 4. Blockquotes -> plain text\n result = result.replaceAll(/^>\\s*(.*)$/gm, \"$1\");\n\n // 5. Escape HTML\n result = result\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\");\n\n // 6. Links\n result = result.replaceAll(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href=\"$2\">$1</a>');\n\n // 7. Bold\n result = result.replaceAll(/\\*\\*(.+?)\\*\\*/g, \"<b>$1</b>\");\n result = result.replaceAll(/__(.+?)__/g, \"<b>$1</b>\");\n\n // 8. Italic\n result = result.replaceAll(\n /(?<![a-zA-Z0-9])_([^_]+)_(?![a-zA-Z0-9])/g,\n \"<i>$1</i>\"\n );\n\n // 9. Strikethrough\n result = result.replaceAll(/~~(.+?)~~/g, \"<s>$1</s>\");\n\n // 10. Bullet lists\n result = result.replaceAll(/^[-*]\\s+/gm, \"\\u2022 \");\n\n // 11. Restore inline code\n for (let i = 0; i < inlineCodes.length; i += 1) {\n const escaped = inlineCodes[i]\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\");\n result = result.replace(`\\u0000IC${i}\\u0000`, `<code>${escaped}</code>`);\n }\n\n // 12. Restore code blocks\n for (let i = 0; i < codeBlocks.length; i += 1) {\n const escaped = codeBlocks[i]\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\");\n result = result.replace(\n `\\u0000CB${i}\\u0000`,\n `<pre><code>${escaped}</code></pre>`\n );\n }\n\n return result;\n}\n\n/**\n * Telegram channel using grammY (long polling).\n */\nexport class TelegramChannel extends BaseChannel {\n readonly name = \"telegram\";\n private bot: Bot | null = null;\n private telegramConfig: TelegramConfig;\n\n constructor(config: TelegramConfig, bus: MessageBus) {\n super(config, bus);\n this.telegramConfig = config;\n }\n\n async start(): Promise<void> {\n if (!this.telegramConfig.token) {\n console.error(\"Telegram bot token not configured\");\n return;\n }\n\n this._running = true;\n this.bot = new Bot(this.telegramConfig.token);\n\n // Handle /start command\n this.bot.command(\"start\", async (ctx) => {\n const user = ctx.from;\n await ctx.reply(\n `Hi ${user?.first_name ?? \"there\"}! I'm nanobot.\\n\\nSend me a message and I'll respond!`\n );\n });\n\n // Handle text messages\n this.bot.on(\"message:text\", async (ctx) => {\n await this.onMessage(ctx);\n });\n\n // Handle photos\n this.bot.on(\"message:photo\", async (ctx) => {\n await this.onMessage(ctx);\n });\n\n // Handle documents\n this.bot.on(\"message:document\", async (ctx) => {\n await this.onMessage(ctx);\n });\n\n console.log(\"Starting Telegram bot (polling mode)...\");\n\n const me = await this.bot.api.getMe();\n console.log(`Telegram bot @${me.username} connected`);\n\n // Start polling with retry — handles 409 Conflict when a stale\n // instance is still holding the long-poll connection.\n const maxRetries = 3;\n for (let attempt = 1; attempt <= maxRetries; attempt += 1) {\n try {\n await this.bot.start({\n allowed_updates: [\"message\"],\n drop_pending_updates: true,\n });\n return; // bot.start() blocks until stopped, so if we get here it was a clean stop\n } catch (error) {\n const is409 = error instanceof Error && error.message.includes(\"409\");\n if (is409 && attempt < maxRetries && this._running) {\n const delay = attempt * 5;\n console.warn(\n `Telegram 409 conflict (attempt ${attempt}/${maxRetries}), retrying in ${delay}s...`\n );\n // oxlint-disable-next-line no-promise-executor-return\n await new Promise((r) => setTimeout(r, delay * 1000));\n continue;\n }\n throw error;\n }\n }\n }\n\n async stop(): Promise<void> {\n this._running = false;\n if (this.bot) {\n console.log(\"Stopping Telegram bot...\");\n await this.bot.stop();\n this.bot = null;\n }\n }\n\n async send(msg: OutboundMessage): Promise<void> {\n if (!this.bot) {\n console.warn(\"Telegram bot not running\");\n return;\n }\n\n try {\n const chatId = Number(msg.chatId);\n if (Number.isNaN(chatId)) {\n console.error(`Invalid chat_id: ${msg.chatId}`);\n return;\n }\n\n const htmlContent = markdownToTelegramHtml(msg.content);\n try {\n await this.bot.api.sendMessage(chatId, htmlContent, {\n parse_mode: \"HTML\",\n });\n } catch {\n // Fallback to plain text\n console.warn(\"HTML parse failed, falling back to plain text\");\n await this.bot.api.sendMessage(chatId, msg.content);\n }\n } catch (error) {\n console.error(\"Error sending Telegram message:\", error);\n }\n }\n\n private async onMessage(ctx: Context): Promise<void> {\n const user = ctx.from;\n const { message } = ctx;\n if (!user || !message) {\n return;\n }\n\n const chatId = message.chat.id;\n let senderId = String(user.id);\n if (user.username) {\n senderId = `${senderId}|${user.username}`;\n }\n\n const contentParts: string[] = [];\n const mediaPaths: string[] = [];\n\n // Text\n if (message.text) {\n contentParts.push(message.text);\n }\n if (message.caption) {\n contentParts.push(message.caption);\n }\n\n // Photos\n if (message.photo && message.photo.length > 0) {\n const photo = message.photo.at(-1);\n try {\n const file = await ctx.api.getFile(photo?.file_id ?? \"\");\n const mediaDir = join(homedir(), \".nanobot\", \"media\");\n if (!existsSync(mediaDir)) {\n mkdirSync(mediaDir, { recursive: true });\n }\n // Note: grammy doesn't have a built-in download_to_drive.\n // In WebContainer context, we just note the file_id.\n contentParts.push(`[image: telegram file ${file.file_id}]`);\n } catch {\n contentParts.push(\"[image: download failed]\");\n }\n }\n\n // Documents\n if (message.document) {\n contentParts.push(\n `[file: ${message.document.file_name ?? message.document.file_id}]`\n );\n }\n\n const content =\n contentParts.length > 0 ? contentParts.join(\"\\n\") : \"[empty message]\";\n\n await this.handleMessage({\n chatId: String(chatId),\n content,\n media: mediaPaths,\n metadata: {\n messageId: message.message_id,\n userId: user.id,\n username: user.username,\n firstName: user.first_name,\n isGroup: message.chat.type !== \"private\",\n },\n senderId,\n });\n }\n}\n"],"mappings":"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"}
|
package/dist/cli/index.mjs
CHANGED
|
@@ -28,11 +28,11 @@ async function loadCustomTools(config) {
|
|
|
28
28
|
} catch {
|
|
29
29
|
instance = exported(entry.options ?? {});
|
|
30
30
|
}
|
|
31
|
-
else throw new
|
|
31
|
+
else throw new TypeError(`Export '${exportName}' is not a function`);
|
|
32
32
|
if (instance && typeof instance === "object" && "execute" in instance && "name" in instance) tools.push(instance);
|
|
33
33
|
else throw new Error(`Export '${exportName}' did not return a Tool instance`);
|
|
34
|
-
} catch (
|
|
35
|
-
console.warn(`Warning: Failed to load custom tool '${entry.module}': ${
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.warn(`Warning: Failed to load custom tool '${entry.module}': ${error instanceof Error ? error.message : String(error)}`);
|
|
36
36
|
}
|
|
37
37
|
return tools;
|
|
38
38
|
}
|
|
@@ -58,6 +58,13 @@ You are a helpful AI assistant. Be concise, accurate, and friendly.
|
|
|
58
58
|
- Ask for clarification when the request is ambiguous
|
|
59
59
|
- Use tools to help accomplish tasks
|
|
60
60
|
- Remember important information in memory/MEMORY.md; past events are logged in memory/HISTORY.md
|
|
61
|
+
`,
|
|
62
|
+
"HEARTBEAT.md": `# Heartbeat
|
|
63
|
+
|
|
64
|
+
This file is checked every 30 minutes. Add tasks or instructions below and the agent will act on them automatically.
|
|
65
|
+
|
|
66
|
+
## Tasks
|
|
67
|
+
|
|
61
68
|
`,
|
|
62
69
|
"IDENTITY.md": `# IDENTITY.md - Who Am I?
|
|
63
70
|
|
|
@@ -103,13 +110,6 @@ Each session, you wake up fresh. These files _are_ your memory. Read them. Updat
|
|
|
103
110
|
Name: (not set)
|
|
104
111
|
Timezone: (not set)
|
|
105
112
|
Language: (not set)
|
|
106
|
-
`,
|
|
107
|
-
"HEARTBEAT.md": `# Heartbeat
|
|
108
|
-
|
|
109
|
-
This file is checked every 30 minutes. Add tasks or instructions below and the agent will act on them automatically.
|
|
110
|
-
|
|
111
|
-
## Tasks
|
|
112
|
-
|
|
113
113
|
`
|
|
114
114
|
})) {
|
|
115
115
|
const filePath = join(workspace, filename);
|
|
@@ -149,7 +149,7 @@ This file stores important information that should persist across sessions.
|
|
|
149
149
|
program.command("gateway").description("Start the nanobot gateway").option("-p, --port <number>", "Gateway port", "18790").option("--verbose", "Verbose output", false).action(async (opts) => {
|
|
150
150
|
console.log(`${LOGO} Starting nanobot gateway on port ${opts.port}...`);
|
|
151
151
|
const config = loadConfig();
|
|
152
|
-
const model = config.agents.defaults
|
|
152
|
+
const { model } = config.agents.defaults;
|
|
153
153
|
const apiKey = getApiKey(config, model);
|
|
154
154
|
const apiBase = getApiBase(config, model);
|
|
155
155
|
const extraHeaders = getExtraHeaders(config, model);
|
|
@@ -165,33 +165,33 @@ program.command("gateway").description("Start the nanobot gateway").option("-p,
|
|
|
165
165
|
const bus = new MessageBus();
|
|
166
166
|
const workspace = getConfigWorkspacePath(config);
|
|
167
167
|
const provider = new OpenAIProvider({
|
|
168
|
-
apiKey,
|
|
169
168
|
apiBase: apiBase ?? void 0,
|
|
169
|
+
apiKey,
|
|
170
170
|
defaultModel: model,
|
|
171
171
|
extraHeaders
|
|
172
172
|
});
|
|
173
173
|
const customTools = await loadCustomTools(config);
|
|
174
174
|
const agent = new AgentLoop({
|
|
175
|
+
braveApiKey: config.tools.web.search.apiKey || void 0,
|
|
175
176
|
bus,
|
|
176
|
-
provider,
|
|
177
|
-
workspace,
|
|
178
|
-
model,
|
|
179
177
|
consolidationModel: resolveConsolidationModel(config.agents.defaults),
|
|
180
|
-
|
|
178
|
+
customTools,
|
|
179
|
+
execConfig: config.tools.exec,
|
|
181
180
|
maxIterations: config.agents.defaults.maxToolIterations,
|
|
181
|
+
maxTokens: config.agents.defaults.maxTokens,
|
|
182
182
|
memoryWindow: config.agents.defaults.memoryWindow,
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
model,
|
|
184
|
+
provider,
|
|
185
185
|
restrictToWorkspace: config.tools.restrictToWorkspace,
|
|
186
|
-
toolsEnabled: config.tools.enabled,
|
|
187
186
|
toolsDisabled: config.tools.disabled,
|
|
188
|
-
|
|
187
|
+
toolsEnabled: config.tools.enabled,
|
|
188
|
+
workspace
|
|
189
189
|
});
|
|
190
190
|
const channels = new ChannelManager(config, bus);
|
|
191
191
|
const { HeartbeatService } = await import("../heartbeat/service.mjs");
|
|
192
192
|
const heartbeat = new HeartbeatService({
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
onHeartbeat: (prompt) => agent.processDirect(prompt, "heartbeat"),
|
|
194
|
+
workspace
|
|
195
195
|
});
|
|
196
196
|
console.log("Heartbeat: managed by DO (local endpoint /api/heartbeat)");
|
|
197
197
|
console.log("Cron: managed by DO");
|
|
@@ -207,21 +207,21 @@ program.command("gateway").description("Start the nanobot gateway").option("-p,
|
|
|
207
207
|
createGatewayServer({
|
|
208
208
|
agent,
|
|
209
209
|
bus,
|
|
210
|
-
port: Number(opts.port),
|
|
211
210
|
channels,
|
|
212
|
-
heartbeat
|
|
211
|
+
heartbeat,
|
|
212
|
+
port: Number(opts.port)
|
|
213
213
|
});
|
|
214
214
|
try {
|
|
215
215
|
await channels.init();
|
|
216
216
|
await Promise.all([agent.run(), channels.startAll()]);
|
|
217
|
-
} catch (
|
|
218
|
-
console.error("Gateway error:",
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.error("Gateway error:", error);
|
|
219
219
|
process.exit(1);
|
|
220
220
|
}
|
|
221
221
|
});
|
|
222
222
|
program.command("agent").description("Interact with the agent directly").option("-m, --message <text>", "Message to send to the agent").option("-s, --session <id>", "Session ID", "cli:default").action(async (opts) => {
|
|
223
223
|
const config = loadConfig();
|
|
224
|
-
const model = config.agents.defaults
|
|
224
|
+
const { model } = config.agents.defaults;
|
|
225
225
|
const apiKey = getApiKey(config, model);
|
|
226
226
|
const apiBase = getApiBase(config, model);
|
|
227
227
|
const extraHeaders = getExtraHeaders(config, model);
|
|
@@ -235,25 +235,25 @@ program.command("agent").description("Interact with the agent directly").option(
|
|
|
235
235
|
const bus = new MessageBus();
|
|
236
236
|
const workspace = getConfigWorkspacePath(config);
|
|
237
237
|
const provider = new OpenAIProvider({
|
|
238
|
-
apiKey,
|
|
239
238
|
apiBase: apiBase ?? void 0,
|
|
239
|
+
apiKey,
|
|
240
240
|
defaultModel: model,
|
|
241
241
|
extraHeaders
|
|
242
242
|
});
|
|
243
243
|
const customTools = await loadCustomTools(config);
|
|
244
244
|
const agentLoop = new AgentLoop({
|
|
245
|
+
braveApiKey: config.tools.web.search.apiKey || void 0,
|
|
245
246
|
bus,
|
|
246
|
-
provider,
|
|
247
|
-
workspace,
|
|
248
247
|
consolidationModel: resolveConsolidationModel(config.agents.defaults),
|
|
248
|
+
customTools,
|
|
249
|
+
execConfig: config.tools.exec,
|
|
249
250
|
maxTokens: config.agents.defaults.maxTokens,
|
|
250
251
|
memoryWindow: config.agents.defaults.memoryWindow,
|
|
251
|
-
|
|
252
|
-
execConfig: config.tools.exec,
|
|
252
|
+
provider,
|
|
253
253
|
restrictToWorkspace: config.tools.restrictToWorkspace,
|
|
254
|
-
toolsEnabled: config.tools.enabled,
|
|
255
254
|
toolsDisabled: config.tools.disabled,
|
|
256
|
-
|
|
255
|
+
toolsEnabled: config.tools.enabled,
|
|
256
|
+
workspace
|
|
257
257
|
});
|
|
258
258
|
if (opts.message) {
|
|
259
259
|
const response = await agentLoop.processDirect(opts.message, opts.session);
|
|
@@ -274,8 +274,8 @@ program.command("agent").description("Interact with the agent directly").option(
|
|
|
274
274
|
try {
|
|
275
275
|
const response = await agentLoop.processDirect(trimmed, opts.session);
|
|
276
276
|
console.log(`\n${LOGO} ${response}\n`);
|
|
277
|
-
} catch (
|
|
278
|
-
console.error("Error:",
|
|
277
|
+
} catch (error) {
|
|
278
|
+
console.error("Error:", error);
|
|
279
279
|
}
|
|
280
280
|
ask();
|
|
281
281
|
});
|