typeclaw 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 (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -0
  3. package/auth.schema.json +63 -0
  4. package/cron.schema.json +96 -0
  5. package/package.json +72 -0
  6. package/scripts/emit-base-dockerfile.ts +5 -0
  7. package/scripts/generate-schema.ts +34 -0
  8. package/secrets.schema.json +63 -0
  9. package/src/agent/auth.ts +119 -0
  10. package/src/agent/compaction.ts +35 -0
  11. package/src/agent/git-nudge.ts +95 -0
  12. package/src/agent/index.ts +451 -0
  13. package/src/agent/plugin-tools.ts +269 -0
  14. package/src/agent/reload-tool.ts +71 -0
  15. package/src/agent/self.ts +45 -0
  16. package/src/agent/session-origin.ts +288 -0
  17. package/src/agent/subagents.ts +253 -0
  18. package/src/agent/system-prompt.ts +68 -0
  19. package/src/agent/tools/channel-fetch-attachment.ts +118 -0
  20. package/src/agent/tools/channel-history.ts +119 -0
  21. package/src/agent/tools/channel-reply.ts +182 -0
  22. package/src/agent/tools/channel-send.ts +212 -0
  23. package/src/agent/tools/ddg.ts +218 -0
  24. package/src/agent/tools/restart.ts +122 -0
  25. package/src/agent/tools/stream-snapshot.ts +181 -0
  26. package/src/agent/tools/webfetch/fetch.ts +102 -0
  27. package/src/agent/tools/webfetch/index.ts +1 -0
  28. package/src/agent/tools/webfetch/strategies/grep.ts +70 -0
  29. package/src/agent/tools/webfetch/strategies/jq.ts +31 -0
  30. package/src/agent/tools/webfetch/strategies/raw.ts +3 -0
  31. package/src/agent/tools/webfetch/strategies/readability.ts +30 -0
  32. package/src/agent/tools/webfetch/strategies/selector.ts +41 -0
  33. package/src/agent/tools/webfetch/strategies/snapshot.ts +135 -0
  34. package/src/agent/tools/webfetch/tool.ts +281 -0
  35. package/src/agent/tools/webfetch/types.ts +33 -0
  36. package/src/agent/tools/websearch.ts +96 -0
  37. package/src/agent/tools/wikipedia.ts +52 -0
  38. package/src/bundled-plugins/agent-browser/dashboard-discovery.ts +170 -0
  39. package/src/bundled-plugins/agent-browser/dashboard-proxy.ts +421 -0
  40. package/src/bundled-plugins/agent-browser/index.ts +179 -0
  41. package/src/bundled-plugins/agent-browser/shim-install.ts +158 -0
  42. package/src/bundled-plugins/agent-browser/shim.ts +152 -0
  43. package/src/bundled-plugins/agent-browser/skills/agent-browser/SKILL.md +113 -0
  44. package/src/bundled-plugins/guard/index.ts +26 -0
  45. package/src/bundled-plugins/guard/policies/non-workspace-write.ts +98 -0
  46. package/src/bundled-plugins/guard/policies/skill-authoring.ts +185 -0
  47. package/src/bundled-plugins/guard/policies/uncommitted-changes.ts +85 -0
  48. package/src/bundled-plugins/guard/policy.ts +18 -0
  49. package/src/bundled-plugins/memory/README.md +71 -0
  50. package/src/bundled-plugins/memory/append-tool.ts +84 -0
  51. package/src/bundled-plugins/memory/dreaming-state.ts +86 -0
  52. package/src/bundled-plugins/memory/dreaming.ts +470 -0
  53. package/src/bundled-plugins/memory/fragment-parser.ts +67 -0
  54. package/src/bundled-plugins/memory/index.ts +238 -0
  55. package/src/bundled-plugins/memory/load-memory.ts +122 -0
  56. package/src/bundled-plugins/memory/memory-logger.ts +257 -0
  57. package/src/bundled-plugins/memory/secret-detector.ts +49 -0
  58. package/src/bundled-plugins/memory/watermark.ts +15 -0
  59. package/src/bundled-plugins/security/index.ts +35 -0
  60. package/src/bundled-plugins/security/policies/git-exfil.ts +120 -0
  61. package/src/bundled-plugins/security/policies/outbound-secret-scan.ts +167 -0
  62. package/src/bundled-plugins/security/policies/prompt-injection.ts +488 -0
  63. package/src/bundled-plugins/security/policies/secret-exfil-bash.ts +99 -0
  64. package/src/bundled-plugins/security/policies/secret-exfil-read.ts +127 -0
  65. package/src/bundled-plugins/security/policies/session-search-secrets.ts +86 -0
  66. package/src/bundled-plugins/security/policies/ssrf.ts +196 -0
  67. package/src/bundled-plugins/security/policies/system-prompt-leak.ts +81 -0
  68. package/src/bundled-plugins/security/policy.ts +9 -0
  69. package/src/channels/adapters/discord-bot-channel-resolver.ts +77 -0
  70. package/src/channels/adapters/discord-bot-classify.ts +148 -0
  71. package/src/channels/adapters/discord-bot.ts +640 -0
  72. package/src/channels/adapters/kakaotalk-author-resolver.ts +78 -0
  73. package/src/channels/adapters/kakaotalk-channel-resolver.ts +105 -0
  74. package/src/channels/adapters/kakaotalk-classify.ts +77 -0
  75. package/src/channels/adapters/kakaotalk.ts +622 -0
  76. package/src/channels/adapters/slack-bot-author-resolver.ts +80 -0
  77. package/src/channels/adapters/slack-bot-channel-resolver.ts +84 -0
  78. package/src/channels/adapters/slack-bot-classify.ts +213 -0
  79. package/src/channels/adapters/slack-bot-dedupe.ts +51 -0
  80. package/src/channels/adapters/slack-bot-time.ts +10 -0
  81. package/src/channels/adapters/slack-bot.ts +881 -0
  82. package/src/channels/adapters/telegram-bot-classify.ts +155 -0
  83. package/src/channels/adapters/telegram-bot-format.ts +309 -0
  84. package/src/channels/adapters/telegram-bot.ts +604 -0
  85. package/src/channels/engagement.ts +227 -0
  86. package/src/channels/index.ts +21 -0
  87. package/src/channels/manager.ts +292 -0
  88. package/src/channels/membership-cache.ts +116 -0
  89. package/src/channels/membership-from-history.ts +53 -0
  90. package/src/channels/membership.ts +30 -0
  91. package/src/channels/participants.ts +47 -0
  92. package/src/channels/persistence.ts +209 -0
  93. package/src/channels/reloadable.ts +28 -0
  94. package/src/channels/router.ts +1570 -0
  95. package/src/channels/schema.ts +273 -0
  96. package/src/channels/types.ts +160 -0
  97. package/src/cli/channel.ts +403 -0
  98. package/src/cli/compose-status.ts +95 -0
  99. package/src/cli/compose.ts +240 -0
  100. package/src/cli/hostd.ts +163 -0
  101. package/src/cli/index.ts +27 -0
  102. package/src/cli/init.ts +592 -0
  103. package/src/cli/logs.ts +38 -0
  104. package/src/cli/reload.ts +68 -0
  105. package/src/cli/restart.ts +66 -0
  106. package/src/cli/run.ts +77 -0
  107. package/src/cli/shell.ts +33 -0
  108. package/src/cli/start.ts +57 -0
  109. package/src/cli/status.ts +178 -0
  110. package/src/cli/stop.ts +31 -0
  111. package/src/cli/tui.ts +35 -0
  112. package/src/cli/ui.ts +110 -0
  113. package/src/commands/index.ts +74 -0
  114. package/src/compose/discover.ts +43 -0
  115. package/src/compose/index.ts +25 -0
  116. package/src/compose/logs.ts +162 -0
  117. package/src/compose/restart.ts +69 -0
  118. package/src/compose/start.ts +62 -0
  119. package/src/compose/status.ts +28 -0
  120. package/src/compose/stop.ts +43 -0
  121. package/src/config/config.ts +424 -0
  122. package/src/config/index.ts +25 -0
  123. package/src/config/providers.ts +234 -0
  124. package/src/config/reloadable.ts +47 -0
  125. package/src/container/index.ts +27 -0
  126. package/src/container/logs.ts +37 -0
  127. package/src/container/port.ts +137 -0
  128. package/src/container/shared.ts +290 -0
  129. package/src/container/shell.ts +58 -0
  130. package/src/container/start.ts +670 -0
  131. package/src/container/status.ts +76 -0
  132. package/src/container/stop.ts +120 -0
  133. package/src/container/verify-running.ts +149 -0
  134. package/src/cron/consumer.ts +138 -0
  135. package/src/cron/index.ts +54 -0
  136. package/src/cron/reloadable.ts +64 -0
  137. package/src/cron/scheduler.ts +200 -0
  138. package/src/cron/schema.ts +96 -0
  139. package/src/hostd/client.ts +113 -0
  140. package/src/hostd/daemon.ts +587 -0
  141. package/src/hostd/index.ts +25 -0
  142. package/src/hostd/paths.ts +82 -0
  143. package/src/hostd/portbroker-manager.ts +101 -0
  144. package/src/hostd/protocol.ts +48 -0
  145. package/src/hostd/spawn.ts +224 -0
  146. package/src/hostd/supervisor.ts +60 -0
  147. package/src/hostd/tailscale.ts +172 -0
  148. package/src/hostd/version.ts +115 -0
  149. package/src/init/dockerfile.ts +327 -0
  150. package/src/init/ensure-deps.ts +152 -0
  151. package/src/init/gitignore.ts +46 -0
  152. package/src/init/hatching.ts +60 -0
  153. package/src/init/index.ts +786 -0
  154. package/src/init/kakaotalk-auth.ts +114 -0
  155. package/src/init/models-dev.ts +130 -0
  156. package/src/init/oauth-login.ts +74 -0
  157. package/src/init/packagejson.ts +94 -0
  158. package/src/init/paths.ts +2 -0
  159. package/src/init/run-bun-install.ts +20 -0
  160. package/src/markdown/chunk.ts +299 -0
  161. package/src/markdown/index.ts +1 -0
  162. package/src/plugin/context.ts +40 -0
  163. package/src/plugin/define.ts +35 -0
  164. package/src/plugin/hooks.ts +204 -0
  165. package/src/plugin/index.ts +63 -0
  166. package/src/plugin/loader.ts +111 -0
  167. package/src/plugin/manager.ts +136 -0
  168. package/src/plugin/registry.ts +145 -0
  169. package/src/plugin/skills.ts +62 -0
  170. package/src/plugin/types.ts +172 -0
  171. package/src/portbroker/bind-with-forward.ts +102 -0
  172. package/src/portbroker/container-server.ts +305 -0
  173. package/src/portbroker/forward-result-bus.ts +36 -0
  174. package/src/portbroker/hostd-client.ts +443 -0
  175. package/src/portbroker/index.ts +33 -0
  176. package/src/portbroker/policy.ts +24 -0
  177. package/src/portbroker/proc-net-tcp.ts +72 -0
  178. package/src/portbroker/protocol.ts +39 -0
  179. package/src/reload/client.ts +59 -0
  180. package/src/reload/index.ts +3 -0
  181. package/src/reload/registry.ts +60 -0
  182. package/src/reload/types.ts +13 -0
  183. package/src/run/bundled-plugins.ts +24 -0
  184. package/src/run/channel-session-factory.ts +105 -0
  185. package/src/run/index.ts +432 -0
  186. package/src/run/plugin-runtime.ts +43 -0
  187. package/src/run/schema-with-plugins.ts +14 -0
  188. package/src/secrets/index.ts +13 -0
  189. package/src/secrets/migrate.ts +95 -0
  190. package/src/secrets/schema.ts +75 -0
  191. package/src/secrets/storage.ts +231 -0
  192. package/src/server/index.ts +436 -0
  193. package/src/sessions/index.ts +23 -0
  194. package/src/shared/index.ts +9 -0
  195. package/src/shared/local-time.ts +21 -0
  196. package/src/shared/protocol.ts +25 -0
  197. package/src/skills/typeclaw-channel-kakaotalk/SKILL.md +87 -0
  198. package/src/skills/typeclaw-channel-telegram-bot/SKILL.md +64 -0
  199. package/src/skills/typeclaw-config/SKILL.md +643 -0
  200. package/src/skills/typeclaw-cron/SKILL.md +159 -0
  201. package/src/skills/typeclaw-git/SKILL.md +89 -0
  202. package/src/skills/typeclaw-memory/SKILL.md +174 -0
  203. package/src/skills/typeclaw-monorepo/SKILL.md +175 -0
  204. package/src/skills/typeclaw-plugins/SKILL.md +594 -0
  205. package/src/skills/typeclaw-skills/SKILL.md +246 -0
  206. package/src/stream/broker.ts +161 -0
  207. package/src/stream/index.ts +16 -0
  208. package/src/stream/types.ts +69 -0
  209. package/src/tui/client.ts +45 -0
  210. package/src/tui/format.ts +317 -0
  211. package/src/tui/index.ts +225 -0
  212. package/src/tui/theme.ts +41 -0
  213. package/typeclaw.schema.json +826 -0
@@ -0,0 +1,78 @@
1
+ import type { KakaoTalkClient } from './kakaotalk'
2
+
3
+ const DEFAULT_TTL_MS = 5 * 60 * 1000
4
+
5
+ export type KakaoAuthorResolver = {
6
+ resolve: (authorId: string, chatId: string) => Promise<string | null>
7
+ }
8
+
9
+ export type KakaoAuthorResolverOptions = {
10
+ client: Pick<KakaoTalkClient, 'getMembers'>
11
+ now?: () => number
12
+ ttlMs?: number
13
+ logger?: { warn: (msg: string) => void }
14
+ }
15
+
16
+ type ChatCacheEntry = {
17
+ members: Map<string, string>
18
+ expiresAt: number
19
+ }
20
+
21
+ // Resolves author IDs to nicknames via LOCO GETMEM (one call per chat per
22
+ // TTL window). Only invoked when the inline `author_name` from the chat-
23
+ // list snapshot is missing — the agent-messenger client already covers
24
+ // "display members" (~5 per chat) for free, and this resolver fills the
25
+ // gap for larger groups and open chats.
26
+ //
27
+ // Lazy by design: a chat's member list is fetched on the first lookup
28
+ // miss, not eagerly on connect. Most agent folders watch a small handful
29
+ // of chats; prefetching every chat's member list at startup would burn
30
+ // LOCO calls on rooms the agent never sees a message in.
31
+ export function createKakaoAuthorResolver(options: KakaoAuthorResolverOptions): KakaoAuthorResolver {
32
+ const now = options.now ?? Date.now
33
+ const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS
34
+ const logger = options.logger
35
+ const cache = new Map<string, ChatCacheEntry>()
36
+ const inflight = new Map<string, Promise<ChatCacheEntry | null>>()
37
+
38
+ const fetchMembers = (chatId: string): Promise<ChatCacheEntry | null> => {
39
+ const existing = inflight.get(chatId)
40
+ if (existing !== undefined) return existing
41
+ const promise = options.client
42
+ .getMembers(chatId)
43
+ .then((members): ChatCacheEntry => {
44
+ const map = new Map<string, string>()
45
+ for (const m of members) map.set(m.user_id, m.nickname)
46
+ const entry: ChatCacheEntry = { members: map, expiresAt: now() + ttlMs }
47
+ cache.set(chatId, entry)
48
+ return entry
49
+ })
50
+ .catch((err: unknown): null => {
51
+ // Author resolution is best-effort. Failing here makes the message
52
+ // render with the raw user_id, which is uglier but still routes.
53
+ logger?.warn(`[kakaotalk] getMembers(${chatId}) failed: ${describe(err)}`)
54
+ return null
55
+ })
56
+ .finally(() => {
57
+ inflight.delete(chatId)
58
+ })
59
+ inflight.set(chatId, promise)
60
+ return promise
61
+ }
62
+
63
+ const resolve = async (authorId: string, chatId: string): Promise<string | null> => {
64
+ const cached = cache.get(chatId)
65
+ if (cached !== undefined && cached.expiresAt > now()) {
66
+ return cached.members.get(authorId) ?? null
67
+ }
68
+ const fresh = await fetchMembers(chatId)
69
+ if (fresh === null) return null
70
+ return fresh.members.get(authorId) ?? null
71
+ }
72
+
73
+ return { resolve }
74
+ }
75
+
76
+ function describe(err: unknown): string {
77
+ return err instanceof Error ? err.message : String(err)
78
+ }
@@ -0,0 +1,105 @@
1
+ import { classifyKakaoChat, type KakaoChat, type KakaoChatKind, type KakaoTalkClient } from 'agent-messenger/kakaotalk'
2
+
3
+ import type { ChannelKey, ChannelNameResolver, ResolvedChannelNames } from '@/channels/types'
4
+
5
+ const DEFAULT_TTL_MS = 5 * 60 * 1000
6
+
7
+ export type KakaoWorkspace = '@kakao-dm' | '@kakao-group' | '@kakao-open'
8
+
9
+ export function kakaoWorkspaceForType(kind: KakaoChatKind): KakaoWorkspace {
10
+ if (kind === 'dm') return '@kakao-dm'
11
+ if (kind === 'open') return '@kakao-open'
12
+ return '@kakao-group'
13
+ }
14
+
15
+ export type KakaoChatLookupValue = {
16
+ workspace: KakaoWorkspace
17
+ isDm: boolean
18
+ }
19
+
20
+ export type KakaoChannelResolver = {
21
+ resolve: ChannelNameResolver
22
+ lookupChat: (chatId: string) => KakaoChatLookupValue | null
23
+ refresh: () => Promise<void>
24
+ }
25
+
26
+ export type KakaoChannelResolverOptions = {
27
+ client: Pick<KakaoTalkClient, 'getChats'>
28
+ now?: () => number
29
+ ttlMs?: number
30
+ logger?: { warn: (msg: string) => void }
31
+ }
32
+
33
+ type Entry = {
34
+ workspace: KakaoWorkspace
35
+ isDm: boolean
36
+ chatName: string | null
37
+ expiresAt: number
38
+ }
39
+
40
+ export function createKakaoChannelResolver(options: KakaoChannelResolverOptions): KakaoChannelResolver {
41
+ const now = options.now ?? Date.now
42
+ const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS
43
+ const cache = new Map<string, Entry>()
44
+ let inflight: Promise<void> | null = null
45
+
46
+ const refresh = async (): Promise<void> => {
47
+ if (inflight !== null) {
48
+ await inflight
49
+ return
50
+ }
51
+ const promise = loadAll().finally(() => {
52
+ inflight = null
53
+ })
54
+ inflight = promise
55
+ await promise
56
+ }
57
+
58
+ const loadAll = async (): Promise<void> => {
59
+ try {
60
+ const chats = await options.client.getChats({ all: true })
61
+ const expiresAt = now() + ttlMs
62
+ for (const chat of chats) ingest(chat, expiresAt)
63
+ } catch (err) {
64
+ options.logger?.warn(`[kakaotalk] channel resolver refresh failed: ${describe(err)}`)
65
+ }
66
+ }
67
+
68
+ const ingest = (chat: KakaoChat, expiresAt: number): void => {
69
+ const kind = classifyKakaoChat(chat)
70
+ const workspace = kakaoWorkspaceForType(kind)
71
+ cache.set(chat.chat_id, {
72
+ workspace,
73
+ isDm: kind === 'dm',
74
+ chatName: chat.title ?? chat.display_name,
75
+ expiresAt,
76
+ })
77
+ }
78
+
79
+ const resolve: ChannelNameResolver = async (key: ChannelKey): Promise<ResolvedChannelNames> => {
80
+ const entry = cache.get(key.chat)
81
+ if (entry === undefined || entry.expiresAt <= now()) await refresh()
82
+ const fresh = cache.get(key.chat)
83
+ if (fresh === undefined) return {}
84
+ const result: ResolvedChannelNames = {}
85
+ if (fresh.chatName !== null && fresh.chatName !== '') result.chatName = fresh.chatName
86
+ return result
87
+ }
88
+
89
+ // Sync lookup. Returns null when the entry is missing OR stale; callers
90
+ // (e.g. inbound classification, history allow checks) MUST treat null as
91
+ // "refresh needed", not "unknown forever". The classifier handles this
92
+ // by awaiting `refresh()` and re-checking before dropping the message —
93
+ // see kakaotalk.ts handleMessageEvent.
94
+ const lookupChat = (chatId: string): KakaoChatLookupValue | null => {
95
+ const entry = cache.get(chatId)
96
+ if (entry === undefined || entry.expiresAt <= now()) return null
97
+ return { workspace: entry.workspace, isDm: entry.isDm }
98
+ }
99
+
100
+ return { resolve, lookupChat, refresh }
101
+ }
102
+
103
+ function describe(err: unknown): string {
104
+ return err instanceof Error ? err.message : String(err)
105
+ }
@@ -0,0 +1,77 @@
1
+ import type { KakaoTalkPushMessageEvent } from 'agent-messenger/kakaotalk'
2
+
3
+ import { matchesAnyAlias } from '@/channels/engagement'
4
+ import { isAllowed, type ChannelAdapterConfig } from '@/channels/schema'
5
+ import type { InboundMessage } from '@/channels/types'
6
+
7
+ export type InboundDropReason = 'self_author' | 'empty_text' | 'unknown_chat' | 'not_in_allow_list' | 'pre_connect'
8
+
9
+ export type InboundClassification =
10
+ | { kind: 'drop'; reason: InboundDropReason }
11
+ | { kind: 'route'; payload: InboundMessage }
12
+
13
+ export type KakaoChatLookup = (chatId: string) => {
14
+ workspace: '@kakao-dm' | '@kakao-group' | '@kakao-open'
15
+ isDm: boolean
16
+ } | null
17
+
18
+ export type KakaoInboundContext = {
19
+ selfUserId: string | null
20
+ lookupChat: KakaoChatLookup
21
+ selfAliases?: readonly string[]
22
+ }
23
+
24
+ export function classifyInbound(
25
+ event: KakaoTalkPushMessageEvent,
26
+ config: ChannelAdapterConfig,
27
+ context: KakaoInboundContext,
28
+ ): InboundClassification {
29
+ if (context.selfUserId === null) {
30
+ return { kind: 'drop', reason: 'pre_connect' }
31
+ }
32
+ if (String(event.author_id) === context.selfUserId) {
33
+ return { kind: 'drop', reason: 'self_author' }
34
+ }
35
+
36
+ const text = event.message ?? ''
37
+ if (text === '') return { kind: 'drop', reason: 'empty_text' }
38
+
39
+ const chatInfo = context.lookupChat(event.chat_id)
40
+ if (chatInfo === null) {
41
+ return { kind: 'drop', reason: 'unknown_chat' }
42
+ }
43
+
44
+ if (!isAllowed(config.allow, chatInfo.workspace, event.chat_id)) {
45
+ return { kind: 'drop', reason: 'not_in_allow_list' }
46
+ }
47
+
48
+ // KakaoTalk has no native @-mention syntax in the LOCO protocol that the
49
+ // SDK exposes (mention rendering happens client-side via display_name
50
+ // matching). Mention-equivalent engagement comes solely from alias
51
+ // matching, which the engagement layer treats as equivalent to a direct
52
+ // mention (see engagement.ts: alias is unconditional and ranks alongside
53
+ // explicit triggers). Without aliases configured, only `reply` and `dm`
54
+ // triggers can fire on KakaoTalk.
55
+ const aliasMatched = matchesAnyAlias(text, context.selfAliases ?? [])
56
+
57
+ return {
58
+ kind: 'route',
59
+ payload: {
60
+ adapter: 'kakaotalk',
61
+ workspace: chatInfo.workspace,
62
+ chat: event.chat_id,
63
+ thread: null,
64
+ text,
65
+ externalMessageId: event.log_id,
66
+ authorId: String(event.author_id),
67
+ authorName: event.author_name ?? String(event.author_id),
68
+ authorIsBot: false,
69
+ isBotMention: aliasMatched,
70
+ replyToBotMessageId: null,
71
+ mentionsOthers: false,
72
+ replyToOtherMessageId: null,
73
+ isDm: chatInfo.isDm,
74
+ ts: event.sent_at,
75
+ },
76
+ }
77
+ }