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.
- package/README.md +15 -9
- package/package.json +5 -3
- package/scripts/dump-system-prompt.ts +12 -1
- package/scripts/require-parallel.ts +41 -0
- package/src/agent/auth.ts +3 -3
- package/src/agent/index.ts +116 -14
- package/src/agent/live-sessions.ts +34 -0
- package/src/agent/multimodal/read-redirect.ts +43 -0
- package/src/agent/plugin-tools.ts +97 -13
- package/src/agent/session-meta.ts +21 -2
- package/src/agent/session-origin.ts +6 -13
- package/src/agent/subagent-completion-reminder.ts +89 -0
- package/src/agent/subagents.ts +3 -2
- package/src/agent/system-prompt.ts +49 -15
- package/src/bundled-plugins/explorer/explorer.ts +2 -2
- package/src/bundled-plugins/guard/index.ts +14 -1
- package/src/bundled-plugins/guard/policies/managed-config.ts +43 -13
- package/src/bundled-plugins/guard/policies/memory-retrieval-cache-write.ts +37 -0
- package/src/bundled-plugins/guard/policies/memory-topics-delete.ts +67 -0
- package/src/bundled-plugins/guard/policies/memory-topics-write.ts +33 -0
- package/src/bundled-plugins/guard/policies/non-workspace-write.ts +8 -2
- package/src/bundled-plugins/guard/policy.ts +7 -0
- package/src/bundled-plugins/memory/README.md +76 -62
- package/src/bundled-plugins/memory/append-tool.ts +3 -2
- package/src/bundled-plugins/memory/citation-superset.ts +49 -11
- package/src/bundled-plugins/memory/citations.ts +19 -8
- package/src/bundled-plugins/memory/delete-tool.ts +57 -0
- package/src/bundled-plugins/memory/dreaming-state.ts +1 -1
- package/src/bundled-plugins/memory/dreaming.ts +364 -146
- package/src/bundled-plugins/memory/frontmatter.ts +165 -0
- package/src/bundled-plugins/memory/index.ts +236 -16
- package/src/bundled-plugins/memory/injection-plan.ts +15 -0
- package/src/bundled-plugins/memory/load-memory.ts +102 -103
- package/src/bundled-plugins/memory/load-shards.ts +156 -0
- package/src/bundled-plugins/memory/memory-logger.ts +16 -15
- package/src/bundled-plugins/memory/memory-retrieval.ts +105 -0
- package/src/bundled-plugins/memory/migration.ts +282 -1
- package/src/bundled-plugins/memory/paths.ts +42 -0
- package/src/bundled-plugins/memory/search-tool.ts +232 -0
- package/src/bundled-plugins/memory/secret-detector.ts +2 -2
- package/src/bundled-plugins/memory/shard-snapshot.ts +51 -0
- package/src/bundled-plugins/memory/slug.ts +59 -0
- package/src/bundled-plugins/memory/stream-io.ts +110 -1
- package/src/bundled-plugins/memory/strength.ts +3 -3
- package/src/bundled-plugins/memory/topics.ts +70 -16
- package/src/bundled-plugins/security/index.ts +24 -0
- package/src/bundled-plugins/security/permissions.ts +4 -0
- package/src/bundled-plugins/security/policies/cron-promotion.ts +349 -0
- package/src/bundled-plugins/security/policies/git-exfil.ts +2 -0
- package/src/bundled-plugins/security/policies/prompt-injection.ts +3 -0
- package/src/bundled-plugins/security/policies/role-promotion.ts +419 -0
- package/src/bundled-plugins/security/policies/system-prompt-leak.ts +1 -0
- package/src/channels/adapters/discord-bot-slash-commands.ts +186 -0
- package/src/channels/adapters/discord-bot.ts +163 -1
- package/src/channels/adapters/kakaotalk-attachment.ts +7 -17
- package/src/channels/adapters/kakaotalk.ts +64 -37
- package/src/channels/adapters/slack-bot-classify.ts +2 -27
- package/src/channels/adapters/slack-bot-slash-commands.ts +82 -0
- package/src/channels/adapters/slack-bot.ts +139 -1
- package/src/channels/index.ts +5 -0
- package/src/channels/router.ts +328 -18
- package/src/channels/subagent-completion-bridge.ts +84 -0
- package/src/cli/builtins.ts +1 -0
- package/src/cli/index.ts +1 -0
- package/src/cli/init.ts +122 -14
- package/src/cli/inspect.ts +151 -0
- package/src/cli/role.ts +7 -2
- package/src/cli/tunnel.ts +13 -1
- package/src/cli/ui.ts +25 -1
- package/src/config/index.ts +1 -0
- package/src/config/models-mutation.ts +10 -2
- package/src/cron/consumer.ts +1 -1
- package/src/init/dockerfile.ts +353 -2
- package/src/init/hatching.ts +5 -6
- package/src/init/kakaotalk-auth.ts +6 -47
- package/src/init/validate-api-key.ts +121 -0
- package/src/inspect/index.ts +213 -0
- package/src/inspect/label.ts +50 -0
- package/src/inspect/live.ts +221 -0
- package/src/inspect/render.ts +163 -0
- package/src/inspect/replay.ts +265 -0
- package/src/inspect/session-list.ts +160 -0
- package/src/inspect/types.ts +110 -0
- package/src/plugin/hooks.ts +23 -1
- package/src/plugin/index.ts +2 -0
- package/src/plugin/manager.ts +1 -1
- package/src/plugin/registry.ts +1 -1
- package/src/plugin/types.ts +10 -0
- package/src/run/channel-session-factory.ts +7 -1
- package/src/run/index.ts +87 -21
- package/src/secrets/kakao-renewal.ts +3 -47
- package/src/server/index.ts +241 -60
- package/src/shared/index.ts +4 -1
- package/src/shared/local-time.ts +17 -0
- package/src/shared/protocol.ts +49 -0
- package/src/skills/typeclaw-channel-kakaotalk/SKILL.md +9 -9
- package/src/skills/typeclaw-claude-code/SKILL.md +83 -40
- package/src/skills/typeclaw-claude-code/references/stop-hook.md +2 -0
- package/src/skills/typeclaw-claude-code/references/tmux-driving.md +102 -16
- package/src/skills/typeclaw-config/SKILL.md +38 -33
- package/src/skills/typeclaw-cron/SKILL.md +1 -1
- package/src/skills/typeclaw-git/SKILL.md +2 -2
- package/src/skills/typeclaw-memory/SKILL.md +16 -163
- package/src/skills/typeclaw-permissions/SKILL.md +2 -2
- package/src/skills/typeclaw-plugins/SKILL.md +26 -15
- package/src/test-helpers/wait-for.ts +7 -1
- 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)
|
package/src/channels/index.ts
CHANGED
|
@@ -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,
|