typeclaw 0.33.0 → 0.34.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/auth.schema.json +66 -0
- package/cron.schema.json +26 -2
- package/package.json +1 -1
- package/secrets.schema.json +66 -0
- package/src/agent/index.ts +7 -3
- package/src/agent/session-origin.ts +17 -0
- package/src/agent/subagent-completion-reminder.ts +14 -1
- package/src/agent/subagent-drain.ts +2 -0
- package/src/agent/subagents.ts +21 -7
- package/src/agent/tools/channel-disengage.ts +66 -0
- package/src/agent/tools/channel-log.ts +3 -2
- package/src/agent/tools/spawn-subagent.ts +25 -5
- package/src/agent/tools/subagent-output.ts +13 -1
- package/src/bundled-plugins/guard/policies/managed-config.ts +1 -1
- package/src/bundled-plugins/memory/memory-logger.ts +7 -0
- package/src/bundled-plugins/researcher/researcher.ts +14 -11
- package/src/bundled-plugins/security/policies/outbound-secret-scan.ts +1 -0
- package/src/channels/adapters/line-channel-resolver.ts +129 -0
- package/src/channels/adapters/line-classify.ts +80 -0
- package/src/channels/adapters/line-format.ts +11 -0
- package/src/channels/adapters/line.ts +350 -0
- package/src/channels/engagement.ts +4 -2
- package/src/channels/manager.ts +65 -6
- package/src/channels/router.ts +186 -41
- package/src/channels/schema.ts +6 -1
- package/src/cli/channel.ts +112 -1
- package/src/cli/cron.ts +22 -4
- package/src/cli/oauth-callbacks.ts +5 -4
- package/src/config/providers.ts +62 -0
- package/src/cron/consumer.ts +33 -0
- package/src/cron/count-state.ts +208 -0
- package/src/cron/index.ts +4 -17
- package/src/cron/list.ts +24 -6
- package/src/cron/scheduler.ts +84 -9
- package/src/cron/schema.ts +100 -13
- package/src/doctor/channel-checks.ts +28 -0
- package/src/hostd/daemon.ts +14 -6
- package/src/hostd/protocol.ts +6 -2
- package/src/init/gitignore.ts +1 -1
- package/src/init/index.ts +36 -3
- package/src/init/line-auth.ts +98 -0
- package/src/init/models-dev.ts +1 -0
- package/src/init/run-owner-claim.ts +1 -0
- package/src/init/validate-api-key.ts +2 -0
- package/src/inspect/label.ts +1 -0
- package/src/permissions/match-rule.ts +28 -12
- package/src/permissions/resolve.ts +8 -1
- package/src/role-claim/match-rule.ts +5 -1
- package/src/run/index.ts +41 -4
- package/src/secrets/line-store.ts +112 -0
- package/src/secrets/oauth-xai.ts +1 -1
- package/src/secrets/schema.ts +25 -0
- package/src/server/index.ts +17 -4
- package/src/shared/protocol.ts +4 -1
- package/src/skills/typeclaw-channel-line/SKILL.md +46 -0
- package/src/skills/typeclaw-channels/SKILL.md +153 -0
- package/src/skills/typeclaw-config/SKILL.md +54 -184
- package/src/skills/typeclaw-config/references/dockerfile.md +66 -0
- package/src/skills/typeclaw-cron/SKILL.md +68 -14
- package/src/skills/typeclaw-permissions/SKILL.md +3 -3
- package/typeclaw.schema.json +167 -3
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: typeclaw-channel-line
|
|
3
|
+
description: Use this skill BEFORE every `channel_reply` or `channel_send` call whose adapter is `line`. LINE renders messages as plain text — `**bold**`, `## headings`, `| tables |`, fenced code blocks, and other markdown all appear literally. There is no `@mention` syntax, no message threads, no replies-with-quote, and no outbound attachments or stickers. Inbound non-text content (images, stickers, files) is not fetchable through this adapter. Read this skill before composing anything on LINE.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# typeclaw-channel-line
|
|
7
|
+
|
|
8
|
+
You are speaking through the `line` channel adapter. The adapter wraps `agent-messenger`'s LineClient/LineListener, which speaks LINE's protocol as a registered sub-device of a real user account.
|
|
9
|
+
|
|
10
|
+
This means **you are messaging as a person, not as a bot.** Other participants see your account's name and profile photo. Match their tone — the people you're talking with assume they're talking to a human.
|
|
11
|
+
|
|
12
|
+
## What LINE does NOT support
|
|
13
|
+
|
|
14
|
+
LINE renders messages as plain text — it has no rich-text formatting. **Write plain text from the start.** The adapter strips common markdown as a safety net before sending (so an accidental `**bold**` won't leak literal asterisks), but treat that as a last-resort guard, not a license to write markdown: the strip removes _markers_, it cannot make formatting-dependent layouts like tables readable. Compose for a plain-text surface and you control the result.
|
|
15
|
+
|
|
16
|
+
Specifically, do not rely on any of the following — write the plain-text equivalent yourself:
|
|
17
|
+
|
|
18
|
+
- **Bold / italic / strikethrough** — emphasize with word choice, not `**asterisks**`.
|
|
19
|
+
- **Headings** — `# H1`, `## H2`, `### H3` carry no visual weight here. Lead with the point.
|
|
20
|
+
- **Tables** — the stripper cannot rescue a pipe-delimited table. Use bullet lists or short prose.
|
|
21
|
+
- **Code fences** — for short snippets, paste the code inline as plain text. For long snippets, summarize and offer to send it another way.
|
|
22
|
+
- **Inline code** — just write `foo`, no backticks.
|
|
23
|
+
- **Links with display text** — send the bare URL on its own line; the LINE client auto-links it. A `[label](url)` that slips through is reduced to `label (url)`, but a bare URL reads cleaner.
|
|
24
|
+
- **Mentions** — there is no `@user` syntax the protocol surfaces. Address people by name in the message body.
|
|
25
|
+
- **Threads / replies-with-quote** — every message is a top-level chat post. There is no per-message reply UI.
|
|
26
|
+
- **Outbound attachments / stickers** — the adapter sends text only. If the user asks you to send a file, image, or sticker, acknowledge the limit and offer text (e.g. paste a link to the file instead).
|
|
27
|
+
|
|
28
|
+
## What LINE DOES support
|
|
29
|
+
|
|
30
|
+
- Plain UTF-8 text. Emoji are fine.
|
|
31
|
+
- URLs auto-linkify in the client. Send them bare — `https://example.com/foo`, no markdown wrapping.
|
|
32
|
+
- Newlines render as line breaks. Use `\n\n` to space paragraphs.
|
|
33
|
+
|
|
34
|
+
## Inbound content
|
|
35
|
+
|
|
36
|
+
Inbound messages are text. LINE may deliver non-text content (images, stickers, files); the adapter surfaces only the text portion and you cannot fetch the bytes through this adapter. If a message arrives with no text, there is nothing for you to act on — do not invent attachment ids.
|
|
37
|
+
|
|
38
|
+
## Chats
|
|
39
|
+
|
|
40
|
+
LINE chats fall into three workspace buckets:
|
|
41
|
+
|
|
42
|
+
- `@line-dm` — a 1:1 direct message.
|
|
43
|
+
- `@line-group` — a group or room (multi-party invite chat).
|
|
44
|
+
- `@line-square` — an OpenChat-style public community. Treat these as the most public surface; be conservative about what you say.
|
|
45
|
+
|
|
46
|
+
Engagement on group and square chats is alias-only (there is no @-mention): you are woken when someone uses one of your configured aliases, replies in a way the engagement layer tracks, or in a DM. In a 1:1 DM every message engages.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: typeclaw-channels
|
|
3
|
+
description: "TypeClaw channel behavior: how the agent decides to engage vs. stay silent on external messenger inbound (Discord, Slack, Telegram, KakaoTalk). Covers the `channels.<adapter>.engagement` triggers (mention/reply/dm), reply stickiness, the non-configurable solo-human fallback, history-prefetch windows, and the `alias` system — plain-text names the agent answers to, substring match semantics, peer-name suppressors, and engagement priority. Load when the user asks why the agent did or did not respond in a channel, wants to change when it auto-replies, asks to 'be quieter'/'stop auto-replying', wants it to answer to a nickname, or mentions engagement, stickiness, aliases, mentions, trigger words, suppressors, or '응답', '호출', '채널', '별칭', '왜 답을 안 해'. Access control (who is admitted at all) lives in `roles` — see typeclaw-permissions. The `channels`/`alias` schema, defaults, and safe-edit workflow live in typeclaw-config."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# typeclaw-channels
|
|
7
|
+
|
|
8
|
+
This skill is the **behavioral contract** for how the agent engages on external messenger channels: when its loop wakes up to reply, when it stays silent and just observes, and how the `alias` name-matching system feeds that decision. It covers the `channels` and `alias` fields of `typeclaw.json`, but from the behavior side.
|
|
9
|
+
|
|
10
|
+
Two adjacent concerns live elsewhere — load the right skill:
|
|
11
|
+
|
|
12
|
+
- **The `channels`/`alias` schema, field types, defaults, and the safe-edit workflow** (read-whole-file, validate, commit, reload-vs-restart) live in the `typeclaw-config` skill. Come back here for what the values _mean_ behaviorally.
|
|
13
|
+
- **Access control — whether an inbound is admitted at all** — lives in `roles`, not `channels`. See the `typeclaw-permissions` skill. Engagement decides whether an _admitted_ inbound wakes the loop; it does not grant visibility.
|
|
14
|
+
|
|
15
|
+
Both `channels` and `alias` are **live-reloadable** — edits take effect on the next `reload`, no container restart.
|
|
16
|
+
|
|
17
|
+
## Channels
|
|
18
|
+
|
|
19
|
+
`channels` configures which external messenger adapters are enabled and how the engagement layer should behave on each. **Access control lives in `roles`, not here** — to admit a chat, declare a role match-rule that covers it (see `typeclaw-permissions`). The shape is `channels: { "<adapter-id>": { engagement, history, enabled } }`. Today the adapters are `discord-bot`, `slack-bot`, `telegram-bot`, and `kakaotalk`.
|
|
20
|
+
|
|
21
|
+
The channels block is **live-reloadable** — edits take effect on the next `reload`, no container restart.
|
|
22
|
+
|
|
23
|
+
### Adapter block
|
|
24
|
+
|
|
25
|
+
Each entry in `channels` is keyed by adapter id and has this shape:
|
|
26
|
+
|
|
27
|
+
| Field | Required | Type | Notes |
|
|
28
|
+
| ------------ | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| `engagement` | no | object | When the agent should auto-reply vs. stay silent. Defaults to mention/reply/dm with 15-minute reply stickiness. See **Engagement** below. |
|
|
30
|
+
| `history` | no | object | Cold-start prefetch windows for `(thread.head, thread.tail, channel.tail)`. Set any to `0` to disable that side. Defaults to `{ thread: { head: 3, tail: 10 }, channel: { tail: 10 } }`. |
|
|
31
|
+
| `enabled` | no | boolean | Defaults to `true`. Set `false` to disable the adapter entirely without removing its config. |
|
|
32
|
+
|
|
33
|
+
To stop the agent answering in a specific channel, narrow the `roles` block so the speaking author's role no longer carries `channel.respond` — engagement triggers gate wake-up _given_ the message is admitted; `channel.respond` gates whether the message is admitted at all.
|
|
34
|
+
|
|
35
|
+
### Engagement
|
|
36
|
+
|
|
37
|
+
`engagement` controls when the agent's loop wakes up to reply on an inbound message it has permission to read. Two fields:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
"engagement": {
|
|
41
|
+
"trigger": ["mention", "reply", "dm"],
|
|
42
|
+
"stickiness": { "perReply": { "window": 900000 } }
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- **`trigger`** — array of any subset of `"mention"`, `"reply"`, `"dm"` (including the empty array `[]`, which disables all explicit triggers — see the quieting playbook below). Default: all three.
|
|
47
|
+
- `mention` — explicit `@bot` mentions.
|
|
48
|
+
- `reply` — message is a Discord reply pointed at the agent's own message.
|
|
49
|
+
- `dm` — any message in a DM channel.
|
|
50
|
+
- **`stickiness`** — either the literal string `"off"`, or `{ perReply: { window: <ms> } }`. Default: 15-minute reply stickiness (`window: 900000`).
|
|
51
|
+
- `perReply` means: after the agent replies to a user, follow-up messages from that same user in that same channel within the window also wake the loop, even without a mention. The window is bounded server-side (`1` to `86_400_000` ms — 1 ms to 24 hours).
|
|
52
|
+
- `"off"` disables stickiness — the agent only wakes on explicit triggers.
|
|
53
|
+
|
|
54
|
+
There is also a **solo-human fallback** built into the runtime that is **not configurable** through `engagement`: in any channel where the participants cache currently holds at most one distinct human author, every admitted inbound wakes the loop, regardless of `trigger` or `stickiness`. The fallback turns off the moment a second distinct human posts in that channel. This makes "private dev channel with one human and the bot" work without forcing an `@mention` on every message; clearing `trigger` to `[]` does **not** override it.
|
|
55
|
+
|
|
56
|
+
**Engagement does not gate access.** Access is gated by `permissions.has(origin, 'channel.respond')` — see the `typeclaw-permissions` skill. Engagement decides whether an _admitted_ inbound wakes the loop or sits in the context buffer.
|
|
57
|
+
|
|
58
|
+
### Example
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
"channels": {
|
|
62
|
+
"discord-bot": {
|
|
63
|
+
"engagement": {
|
|
64
|
+
"trigger": ["mention", "reply", "dm"],
|
|
65
|
+
"stickiness": { "perReply": { "window": 900000 } }
|
|
66
|
+
},
|
|
67
|
+
"enabled": true
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"roles": {
|
|
71
|
+
"member": { "match": ["discord:123456789012345678/987654321098765432", "discord:dm/*"] }
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This says: the `discord-bot` adapter is enabled with default engagement; one specific channel in one specific guild plus all DMs admit speakers as `member` (which carries `channel.respond` by default).
|
|
76
|
+
|
|
77
|
+
### When the user asks "let me talk to you in this channel"
|
|
78
|
+
|
|
79
|
+
This is a **`roles`** edit, not a `channels` edit. See the `typeclaw-permissions` skill for the full procedure. Short version:
|
|
80
|
+
|
|
81
|
+
1. Get the platform ID (Discord channel ID, Slack channel ID, Telegram chat ID, KakaoTalk chat ID).
|
|
82
|
+
2. Append a match-rule to `roles.member.match` using the canonical DSL (`discord:<guild>/<channel>`, `slack:<team>/<channel>`, `telegram:<chat>`, `kakao:<chat>`). Pass `acknowledgeGuards: { rolePromotion: true }` in the `write`/`edit` args — the `rolePromotion` security guard blocks any widening of `roles.<role>.match` without an ack (see `typeclaw-permissions`).
|
|
83
|
+
3. **`roles.<role>.match[]` edits are live-reloadable** — they take effect on the next `typeclaw reload` (the classifier marks `roles.match` as `applied`, and the permission service rebuilds its role table). Only `roles.<role>.permissions[]` edits are restart-required. So adding a match-rule to admit a channel applies on `reload`; no container restart needed.
|
|
84
|
+
|
|
85
|
+
### When the user asks "stop replying in this channel"
|
|
86
|
+
|
|
87
|
+
Two interpretations — ask if unclear:
|
|
88
|
+
|
|
89
|
+
- **"Stop everything"** — remove the match-rule from `roles.<role>.match`. The agent loses both inbound visibility and outbound posting on that channel.
|
|
90
|
+
- **"Just stop auto-replying"** — leave the match-rule, but adjust `engagement` on the adapter (set `trigger: []` and/or `stickiness: "off"`). The agent can still receive the channel and can still post if you tell it to. Caveat: this approach does NOT silence the agent in a channel that currently has only one human posting — the solo-human fallback (see Engagement) overrides `trigger: []`. In that case the only way to go silent today is to remove the match-rule.
|
|
91
|
+
|
|
92
|
+
The second is usually what people mean by "be quieter".
|
|
93
|
+
|
|
94
|
+
### When the user asks "what channels can you see / are you in"
|
|
95
|
+
|
|
96
|
+
1. **Read `typeclaw.json`**, list each adapter under `channels`: which is enabled, the engagement triggers and stickiness window.
|
|
97
|
+
2. Also read `roles.<role>.match` for every role — those are the actual admit lists.
|
|
98
|
+
3. Note that the live runtime may have a different view if `typeclaw.json` was edited but `reload` hasn't run yet — say so when relevant.
|
|
99
|
+
|
|
100
|
+
## Alias
|
|
101
|
+
|
|
102
|
+
`alias` is an array of plain-text names the agent answers to when a channel message contains the name without using the platform's `<@id>` mention syntax. It is independent from `channels.<adapter>.engagement.trigger`: the structural triggers (`mention`, `reply`, `dm`) gate engagement on platform-rendered events; `alias` gates engagement on the message text itself.
|
|
103
|
+
|
|
104
|
+
The agent folder's directory name (`basename(agentDir)`) is **always** an implicit alias — the runtime adds it automatically. `alias` adds further forms on top: Latin transliteration of a Korean nickname, casual short forms, alternative spellings, etc. **You only need to add the dir-name explicitly when you want a variation of it** (different casing, a different word entirely, or extra forms beyond the dir name).
|
|
105
|
+
|
|
106
|
+
### Match semantics
|
|
107
|
+
|
|
108
|
+
- **Substring** match against the inbound text. `"토토"` matches `"토토아 cron"`, `"토토씨 안녕"`, `"누가 토토을 불러"`, all of them. Korean particles aren't stripped — substring is enough because the bot name appears at the start of every particled form.
|
|
109
|
+
- **Case-insensitive** via `toLocaleLowerCase()` on both sides. `"Toto"` in the alias list matches `"TOTO"`, `"toto"`, `"ToTo"`.
|
|
110
|
+
- **No word-boundary detection.** A short or generic alias like `"bot"` will match every message containing `"robot"` or `"bottom"`. Pick distinctive names — the operator owns curation.
|
|
111
|
+
|
|
112
|
+
### Engagement priority
|
|
113
|
+
|
|
114
|
+
The alias path runs **after** explicit triggers (mention/reply/dm) and the sticky check. So a message with both an `<@id>` mention and an alias substring engages once, normally. A message with only the alias substring engages on the alias path. The alias path is **NOT suppressed by `mentionsOthers`**: addressing two bots in one message (`"토토아 라라아 둘 다 봐"`) engages both bots — each on their own alias.
|
|
115
|
+
|
|
116
|
+
There's also a symmetric **peer-name suppressor**: if the message contains a peer bot's observed display name (from `participants[]`, populated as peers speak in the channel) and **does not** contain any of this agent's aliases, the solo-human fallback is suppressed and the agent observes. This is what makes `"라라아 cron 좀"` in a 1-human-multi-bot channel correctly observe instead of all bots replying. First-time addressing of a never-seen peer slips through; the suppressor catches it after the peer's first message.
|
|
117
|
+
|
|
118
|
+
### Example
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"alias": ["toto", "토토"]
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The agent in folder `토토/` already answers to `"토토"` from the dir name. This adds the Latin transliteration so users can also write `"Hey toto, deploy?"`.
|
|
127
|
+
|
|
128
|
+
### When the user asks "respond to my casual nickname for you" / "I want to call you X"
|
|
129
|
+
|
|
130
|
+
1. **Read `typeclaw.json`.**
|
|
131
|
+
2. **If `alias` exists**, append the new name (preserve existing entries; dedupe trivially — the runtime also dedupes).
|
|
132
|
+
3. **If `alias` is absent**, create it as `["<new name>"]`.
|
|
133
|
+
4. **You don't need to add the dir name** unless the new name IS a variation of the dir name itself (e.g. dir is `toto` and the user wants `Toto` casing — the implicit dir alias matches case-insensitively, so this isn't needed either).
|
|
134
|
+
5. **Trim whitespace** before adding. The schema rejects empty/whitespace-only entries; the runtime trims surrounding whitespace from valid entries.
|
|
135
|
+
6. **Write, commit**: "Edited `alias` — live-reloadable. Run `reload` to pick up the change without restart."
|
|
136
|
+
|
|
137
|
+
### When the user asks "stop responding to <name>"
|
|
138
|
+
|
|
139
|
+
1. **Read `typeclaw.json`.**
|
|
140
|
+
2. **Remove the entry** from `alias`. If the entry IS the dir name, removing it from `alias` does nothing — the dir name is implicit and can't be turned off this way. The right answer there is "to stop responding to your dir name, rename the agent folder, which is a host-stage operation outside this container."
|
|
141
|
+
3. **Write, commit**: "Edited `alias` — live-reloadable. Run `reload` to pick up the change without restart."
|
|
142
|
+
|
|
143
|
+
### When the user asks "what names do you respond to"
|
|
144
|
+
|
|
145
|
+
1. **Read `typeclaw.json`** and report `alias`.
|
|
146
|
+
2. **Always also report `basename(agentDir)`** (the implicit dir-name alias) — the user might not realize it's automatic.
|
|
147
|
+
3. Mention that channel addressing also engages on `<@id>` mentions and replies regardless of alias config (those are separate triggers in `channels.<adapter>.engagement`).
|
|
148
|
+
|
|
149
|
+
## What this skill does _not_ cover
|
|
150
|
+
|
|
151
|
+
- **The `channels`/`alias` schema, field types, defaults, and the safe-edit workflow** (read-whole-file, validate, write-back, commit) — see the `typeclaw-config` skill.
|
|
152
|
+
- **Access control — who is admitted to a channel at all** (`roles`, match-rule DSL, `channel.respond`, the `rolePromotion` guard) — see the `typeclaw-permissions` skill. Engagement only decides whether an _admitted_ inbound wakes the loop.
|
|
153
|
+
- **Channel credentials** (`secrets.json#channels.<adapter>`, bot tokens) — see the `typeclaw-config` skill's provider-credentials section.
|