typeclaw 0.7.0 → 0.9.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 (107) hide show
  1. package/README.md +15 -9
  2. package/package.json +5 -3
  3. package/scripts/dump-system-prompt.ts +12 -1
  4. package/scripts/require-parallel.ts +41 -0
  5. package/src/agent/auth.ts +3 -3
  6. package/src/agent/index.ts +116 -14
  7. package/src/agent/live-sessions.ts +34 -0
  8. package/src/agent/multimodal/read-redirect.ts +43 -0
  9. package/src/agent/plugin-tools.ts +97 -13
  10. package/src/agent/session-meta.ts +21 -2
  11. package/src/agent/session-origin.ts +6 -13
  12. package/src/agent/subagent-completion-reminder.ts +89 -0
  13. package/src/agent/subagents.ts +3 -2
  14. package/src/agent/system-prompt.ts +49 -15
  15. package/src/bundled-plugins/explorer/explorer.ts +2 -2
  16. package/src/bundled-plugins/guard/index.ts +14 -1
  17. package/src/bundled-plugins/guard/policies/managed-config.ts +43 -13
  18. package/src/bundled-plugins/guard/policies/memory-retrieval-cache-write.ts +37 -0
  19. package/src/bundled-plugins/guard/policies/memory-topics-delete.ts +67 -0
  20. package/src/bundled-plugins/guard/policies/memory-topics-write.ts +33 -0
  21. package/src/bundled-plugins/guard/policies/non-workspace-write.ts +8 -2
  22. package/src/bundled-plugins/guard/policy.ts +7 -0
  23. package/src/bundled-plugins/memory/README.md +76 -62
  24. package/src/bundled-plugins/memory/append-tool.ts +3 -2
  25. package/src/bundled-plugins/memory/citation-superset.ts +49 -11
  26. package/src/bundled-plugins/memory/citations.ts +19 -8
  27. package/src/bundled-plugins/memory/delete-tool.ts +57 -0
  28. package/src/bundled-plugins/memory/dreaming-state.ts +1 -1
  29. package/src/bundled-plugins/memory/dreaming.ts +364 -146
  30. package/src/bundled-plugins/memory/frontmatter.ts +165 -0
  31. package/src/bundled-plugins/memory/index.ts +236 -16
  32. package/src/bundled-plugins/memory/injection-plan.ts +15 -0
  33. package/src/bundled-plugins/memory/load-memory.ts +102 -103
  34. package/src/bundled-plugins/memory/load-shards.ts +156 -0
  35. package/src/bundled-plugins/memory/memory-logger.ts +16 -15
  36. package/src/bundled-plugins/memory/memory-retrieval.ts +105 -0
  37. package/src/bundled-plugins/memory/migration.ts +282 -1
  38. package/src/bundled-plugins/memory/paths.ts +42 -0
  39. package/src/bundled-plugins/memory/search-tool.ts +232 -0
  40. package/src/bundled-plugins/memory/secret-detector.ts +2 -2
  41. package/src/bundled-plugins/memory/shard-snapshot.ts +51 -0
  42. package/src/bundled-plugins/memory/slug.ts +59 -0
  43. package/src/bundled-plugins/memory/stream-io.ts +110 -1
  44. package/src/bundled-plugins/memory/strength.ts +3 -3
  45. package/src/bundled-plugins/memory/topics.ts +70 -16
  46. package/src/bundled-plugins/security/index.ts +24 -0
  47. package/src/bundled-plugins/security/permissions.ts +4 -0
  48. package/src/bundled-plugins/security/policies/cron-promotion.ts +349 -0
  49. package/src/bundled-plugins/security/policies/git-exfil.ts +2 -0
  50. package/src/bundled-plugins/security/policies/prompt-injection.ts +3 -0
  51. package/src/bundled-plugins/security/policies/role-promotion.ts +419 -0
  52. package/src/bundled-plugins/security/policies/system-prompt-leak.ts +1 -0
  53. package/src/channels/adapters/discord-bot-slash-commands.ts +186 -0
  54. package/src/channels/adapters/discord-bot.ts +163 -1
  55. package/src/channels/adapters/kakaotalk-attachment.ts +7 -17
  56. package/src/channels/adapters/kakaotalk.ts +64 -37
  57. package/src/channels/adapters/slack-bot-classify.ts +2 -27
  58. package/src/channels/adapters/slack-bot-slash-commands.ts +82 -0
  59. package/src/channels/adapters/slack-bot.ts +139 -1
  60. package/src/channels/index.ts +5 -0
  61. package/src/channels/router.ts +328 -18
  62. package/src/channels/subagent-completion-bridge.ts +84 -0
  63. package/src/cli/builtins.ts +1 -0
  64. package/src/cli/index.ts +1 -0
  65. package/src/cli/init.ts +122 -14
  66. package/src/cli/inspect.ts +151 -0
  67. package/src/cli/role.ts +7 -2
  68. package/src/cli/tunnel.ts +13 -1
  69. package/src/cli/ui.ts +25 -1
  70. package/src/config/index.ts +1 -0
  71. package/src/config/models-mutation.ts +10 -2
  72. package/src/cron/consumer.ts +1 -1
  73. package/src/init/dockerfile.ts +353 -2
  74. package/src/init/hatching.ts +5 -6
  75. package/src/init/kakaotalk-auth.ts +6 -47
  76. package/src/init/validate-api-key.ts +121 -0
  77. package/src/inspect/index.ts +213 -0
  78. package/src/inspect/label.ts +50 -0
  79. package/src/inspect/live.ts +221 -0
  80. package/src/inspect/render.ts +163 -0
  81. package/src/inspect/replay.ts +265 -0
  82. package/src/inspect/session-list.ts +160 -0
  83. package/src/inspect/types.ts +110 -0
  84. package/src/plugin/hooks.ts +23 -1
  85. package/src/plugin/index.ts +2 -0
  86. package/src/plugin/manager.ts +1 -1
  87. package/src/plugin/registry.ts +1 -1
  88. package/src/plugin/types.ts +10 -0
  89. package/src/run/channel-session-factory.ts +7 -1
  90. package/src/run/index.ts +87 -21
  91. package/src/secrets/kakao-renewal.ts +3 -47
  92. package/src/server/index.ts +241 -60
  93. package/src/shared/index.ts +4 -1
  94. package/src/shared/local-time.ts +17 -0
  95. package/src/shared/protocol.ts +49 -0
  96. package/src/skills/typeclaw-channel-kakaotalk/SKILL.md +9 -9
  97. package/src/skills/typeclaw-claude-code/SKILL.md +83 -40
  98. package/src/skills/typeclaw-claude-code/references/stop-hook.md +2 -0
  99. package/src/skills/typeclaw-claude-code/references/tmux-driving.md +102 -16
  100. package/src/skills/typeclaw-config/SKILL.md +38 -33
  101. package/src/skills/typeclaw-cron/SKILL.md +1 -1
  102. package/src/skills/typeclaw-git/SKILL.md +2 -2
  103. package/src/skills/typeclaw-memory/SKILL.md +16 -163
  104. package/src/skills/typeclaw-permissions/SKILL.md +2 -2
  105. package/src/skills/typeclaw-plugins/SKILL.md +26 -15
  106. package/src/test-helpers/wait-for.ts +7 -1
  107. package/typeclaw.schema.json +7 -0
@@ -1,4 +1,4 @@
1
- import { SlackBotClient, SlackBotListener } from 'agent-messenger/slackbot'
1
+ import { SlackBotClient, SlackBotListener, type SlackSocketModeSlashCommandArgs } from 'agent-messenger/slackbot'
2
2
 
3
3
  import {
4
4
  MEMBERSHIP_ENUMERATION_CAP,
@@ -33,8 +33,27 @@ import {
33
33
  type SlackInboundMessageEvent,
34
34
  } from './slack-bot-classify'
35
35
  import { createSlackDedupe } from './slack-bot-dedupe'
36
+ import {
37
+ buildSlashAckPayload,
38
+ parseSlashCommand,
39
+ SLACK_SLASH_REPLY_ABORTED,
40
+ SLACK_SLASH_REPLY_AMBIGUOUS,
41
+ SLACK_SLASH_REPLY_FAILED,
42
+ SLACK_SLASH_REPLY_NO_LIVE_SESSION,
43
+ SLACK_SLASH_REPLY_PERMISSION_DENIED,
44
+ } from './slack-bot-slash-commands'
36
45
  import { slackTsToMillis } from './slack-bot-time'
37
46
 
47
+ // One slash command per logical agent gesture. Mirrors the discord-bot
48
+ // SLASH_COMMANDS constant so the cross-platform set stays consistent — when
49
+ // we add a new command (e.g. /memory), it appears in both adapters together.
50
+ // The actual registration lives in the Slack App Manifest at src/cli/ui.ts;
51
+ // this constant is the runtime allow-list that gates which delivered
52
+ // slash_commands events we route vs drop. The ui.test.ts manifest-drift
53
+ // test asserts equality between this set and SLACK_APP_MANIFEST.features.
54
+ // slash_commands so the two can never silently diverge.
55
+ export const SLACK_SLASH_COMMAND_NAMES: ReadonlySet<string> = new Set(['stop'])
56
+
38
57
  // Resolvers fall back to the raw id on failure, so a name equal to the id
39
58
  // means resolution failed; we render the bare id rather than `id(id)`. The
40
59
  // prefix is intentionally only applied to the named form so we never log
@@ -44,6 +63,101 @@ function formatLabel(name: string | undefined, id: string, prefix = ''): string
44
63
  return `${prefix}${name}(${id})`
45
64
  }
46
65
 
66
+ export type SlackBotAdapterLoggerLike = {
67
+ info: (msg: string) => void
68
+ warn: (msg: string) => void
69
+ error: (msg: string) => void
70
+ }
71
+
72
+ export type SlashCommandHandlerDeps = {
73
+ router: Pick<ChannelRouter, 'executeCommand'>
74
+ knownCommandNames: ReadonlySet<string>
75
+ logger: SlackBotAdapterLoggerLike
76
+ formatChannelTag: (workspace: string, chat: string) => Promise<string>
77
+ }
78
+
79
+ // Ack-first invariant: the handler must call args.ack() exactly once on
80
+ // every path AND must do so before any slow network work (resolver calls,
81
+ // post-ack logging). Slack's 3s ack deadline starts when the slash command
82
+ // envelope arrives on the WebSocket; missing it shows the user
83
+ // "/stop didn't respond in time". The synchronous executeCommand happy
84
+ // path is fast (in-memory map lookup + abort), so ack-after-execute is
85
+ // safe; everything else (formatChannelTag, post-ack logging) runs after.
86
+ //
87
+ // Ack failure handling: a thrown ack on the happy path is logged but does
88
+ // NOT trigger the catch-all error-ack below, which would attempt a second
89
+ // ack call and break the exactly-once contract.
90
+ export function createSlashCommandHandler(
91
+ deps: SlashCommandHandlerDeps,
92
+ ): (args: SlackSocketModeSlashCommandArgs) => Promise<void> {
93
+ return async ({ ack, body }) => {
94
+ const parsed = parseSlashCommand(body, deps.knownCommandNames)
95
+ if (parsed.kind === 'ignore') {
96
+ deps.logger.warn(`[slack-bot] slash command dropped reason=${parsed.reason} command=${body.command}`)
97
+ try {
98
+ ack(buildSlashAckPayload(SLACK_SLASH_REPLY_FAILED))
99
+ } catch (err) {
100
+ deps.logger.warn(`[slack-bot] slash command ack (drop path) failed: ${describe(err)}`)
101
+ }
102
+ return
103
+ }
104
+ const { command } = parsed
105
+
106
+ // Pre-ACK log: bare ids only (no formatChannelTag — would burn ack budget
107
+ // on a slow Slack API minute via the channel-name resolver).
108
+ deps.logger.info(
109
+ `[slack-bot] slash /${command.name} invoker=${command.invokerId} team=${command.key.workspace} channel=${command.key.chat}`,
110
+ )
111
+
112
+ let result: Awaited<ReturnType<typeof deps.router.executeCommand>>
113
+ try {
114
+ result = await deps.router.executeCommand(command.key, command.name, {
115
+ invokerId: command.invokerId,
116
+ })
117
+ } catch (err) {
118
+ deps.logger.error(`[slack-bot] slash command handler failed: ${describe(err)}`)
119
+ try {
120
+ ack(buildSlashAckPayload(SLACK_SLASH_REPLY_FAILED))
121
+ } catch (ackErr) {
122
+ deps.logger.warn(`[slack-bot] slash command error-ack failed: ${describe(ackErr)}`)
123
+ }
124
+ return
125
+ }
126
+
127
+ const replyContent =
128
+ result.kind === 'handled'
129
+ ? SLACK_SLASH_REPLY_ABORTED
130
+ : result.kind === 'no-live-session'
131
+ ? SLACK_SLASH_REPLY_NO_LIVE_SESSION
132
+ : result.kind === 'permission-denied'
133
+ ? SLACK_SLASH_REPLY_PERMISSION_DENIED
134
+ : result.kind === 'ambiguous'
135
+ ? SLACK_SLASH_REPLY_AMBIGUOUS
136
+ : SLACK_SLASH_REPLY_FAILED
137
+
138
+ // Final ack on the happy path: own try/catch so a thrown ack here does
139
+ // NOT cascade into the error-path ack above (which would violate the
140
+ // exactly-once contract). The abort already happened server-side; only
141
+ // the user-visible confirmation is lost.
142
+ try {
143
+ ack(buildSlashAckPayload(replyContent))
144
+ } catch (err) {
145
+ deps.logger.warn(`[slack-bot] slash command ack failed: ${describe(err)}`)
146
+ }
147
+
148
+ // Decorative post-ack logging: resolve channel names now that the 3s
149
+ // budget is no longer a concern. Best-effort.
150
+ try {
151
+ const inboundTag = await deps.formatChannelTag(command.key.workspace, command.key.chat)
152
+ deps.logger.info(`[slack-bot] slash /${command.name} result=${result.kind} ${inboundTag}`)
153
+ } catch (err) {
154
+ deps.logger.info(
155
+ `[slack-bot] slash /${command.name} result=${result.kind} (channel-tag resolution failed: ${describe(err)})`,
156
+ )
157
+ }
158
+ }
159
+ }
160
+
47
161
  // app_mention payloads omit channel_type and never carry a subtype, so we
48
162
  // promote them to a message-shaped event for the shared classifier. The
49
163
  // promoted event is classified as a regular channel message; the
@@ -661,6 +775,13 @@ export function createSlackBotAdapter(options: SlackBotAdapterOptions): SlackBot
661
775
 
662
776
  const dedupe = createSlackDedupe()
663
777
 
778
+ const handleSlashCommand = createSlashCommandHandler({
779
+ router: options.router,
780
+ knownCommandNames: SLACK_SLASH_COMMAND_NAMES,
781
+ logger,
782
+ formatChannelTag,
783
+ })
784
+
664
785
  const handleMessageEvent = async (
665
786
  event: SlackInboundMessageEvent,
666
787
  source: 'message' | 'app_mention',
@@ -777,6 +898,23 @@ export function createSlackBotAdapter(options: SlackBotAdapterOptions): SlackBot
777
898
  ack()
778
899
  void handleMessageEvent(promoteAppMentionToMessage(event as SlackInboundAppMentionEvent), 'app_mention')
779
900
  })
901
+ listener.on('slash_commands', (args) => {
902
+ // The handler owns the ack call itself (the ack payload carries the
903
+ // user-visible reply text), so we do NOT ack here. inflightInbounds
904
+ // wrapping mirrors handleMessageEvent so stop() can drain the
905
+ // handler before tearing down the listener — otherwise a /stop
906
+ // arriving during stop() would lose its ack and the user sees
907
+ // "didn't respond in time" even though the abort succeeded.
908
+ inflightInbounds++
909
+ void handleSlashCommand(args).finally(() => {
910
+ inflightInbounds--
911
+ if (inflightInbounds === 0 && stopWaiters.length > 0) {
912
+ const waiters = stopWaiters
913
+ stopWaiters = []
914
+ for (const w of waiters) w()
915
+ }
916
+ })
917
+ })
780
918
 
781
919
  options.router.registerOutbound('slack-bot', outboundCallback)
782
920
  options.router.registerTyping('slack-bot', typingCallback)
@@ -9,6 +9,11 @@ export {
9
9
  type CreateSessionForChannel,
10
10
  } from './router'
11
11
  export { createChannelsReloadable } from './reloadable'
12
+ export {
13
+ createSubagentCompletionBridge,
14
+ type SubagentCompletionBridge,
15
+ type SubagentCompletionBridgeOptions,
16
+ } from './subagent-completion-bridge'
12
17
  export {
13
18
  channelsSchema,
14
19
  ADAPTER_IDS,