typeclaw 0.32.1 → 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.
Files changed (65) hide show
  1. package/auth.schema.json +66 -0
  2. package/cron.schema.json +26 -2
  3. package/package.json +1 -1
  4. package/secrets.schema.json +66 -0
  5. package/src/agent/index.ts +7 -3
  6. package/src/agent/session-origin.ts +17 -0
  7. package/src/agent/subagent-completion-reminder.ts +14 -1
  8. package/src/agent/subagent-drain.ts +2 -0
  9. package/src/agent/subagents.ts +21 -7
  10. package/src/agent/tools/channel-disengage.ts +66 -0
  11. package/src/agent/tools/channel-log.ts +3 -2
  12. package/src/agent/tools/spawn-subagent.ts +25 -5
  13. package/src/agent/tools/subagent-output.ts +13 -1
  14. package/src/bundled-plugins/guard/policies/managed-config.ts +1 -1
  15. package/src/bundled-plugins/memory/memory-logger.ts +7 -0
  16. package/src/bundled-plugins/researcher/researcher.ts +14 -11
  17. package/src/bundled-plugins/security/policies/outbound-secret-scan.ts +2 -0
  18. package/src/channels/adapters/line-channel-resolver.ts +129 -0
  19. package/src/channels/adapters/line-classify.ts +80 -0
  20. package/src/channels/adapters/line-format.ts +11 -0
  21. package/src/channels/adapters/line.ts +350 -0
  22. package/src/channels/engagement.ts +4 -2
  23. package/src/channels/manager.ts +65 -6
  24. package/src/channels/router.ts +186 -41
  25. package/src/channels/schema.ts +6 -1
  26. package/src/cli/channel.ts +112 -1
  27. package/src/cli/cron.ts +22 -4
  28. package/src/cli/init.ts +267 -82
  29. package/src/cli/model.ts +5 -1
  30. package/src/cli/oauth-callbacks.ts +5 -4
  31. package/src/cli/provider.ts +41 -10
  32. package/src/config/providers.ts +366 -7
  33. package/src/cron/consumer.ts +33 -0
  34. package/src/cron/count-state.ts +208 -0
  35. package/src/cron/index.ts +4 -17
  36. package/src/cron/list.ts +24 -6
  37. package/src/cron/scheduler.ts +84 -9
  38. package/src/cron/schema.ts +100 -13
  39. package/src/doctor/channel-checks.ts +28 -0
  40. package/src/hostd/daemon.ts +14 -6
  41. package/src/hostd/protocol.ts +6 -2
  42. package/src/init/gitignore.ts +1 -1
  43. package/src/init/index.ts +36 -3
  44. package/src/init/line-auth.ts +98 -0
  45. package/src/init/models-dev.ts +3 -0
  46. package/src/init/run-owner-claim.ts +1 -0
  47. package/src/init/validate-api-key.ts +15 -0
  48. package/src/inspect/label.ts +1 -0
  49. package/src/permissions/match-rule.ts +28 -12
  50. package/src/permissions/resolve.ts +8 -1
  51. package/src/role-claim/match-rule.ts +5 -1
  52. package/src/run/index.ts +41 -4
  53. package/src/secrets/line-store.ts +112 -0
  54. package/src/secrets/oauth-xai.ts +342 -0
  55. package/src/secrets/schema.ts +25 -0
  56. package/src/secrets/storage.ts +2 -0
  57. package/src/server/index.ts +17 -4
  58. package/src/shared/protocol.ts +4 -1
  59. package/src/skills/typeclaw-channel-line/SKILL.md +46 -0
  60. package/src/skills/typeclaw-channels/SKILL.md +153 -0
  61. package/src/skills/typeclaw-config/SKILL.md +54 -184
  62. package/src/skills/typeclaw-config/references/dockerfile.md +66 -0
  63. package/src/skills/typeclaw-cron/SKILL.md +68 -14
  64. package/src/skills/typeclaw-permissions/SKILL.md +3 -3
  65. package/typeclaw.schema.json +185 -3
@@ -0,0 +1,66 @@
1
+ # `docker.file` reference — toggle catalog and build internals
2
+
3
+ Companion to the **Dockerfile** section of the `typeclaw-config` SKILL.md. The SKILL.md owns the entry point and the per-question playbooks; this file is the lookup table for the individual `docker.file` toggles and the build-layer internals. Consult it when the user wants a specific package toggled/pinned, or asks how the toggles land in the image.
4
+
5
+ ## Toggle fields
6
+
7
+ `docker.file` has two layers of customization:
8
+
9
+ 1. **Toggles** for opinionated package installs typeclaw knows how to layer correctly (`tmux`, `gh`, `python`, `ffmpeg`, `cjkFonts`, `cloudflared`, `xvfb`, `claudeCode`, `codexCli`). Most are apt packages — boolean for on/off, version string for an apt pin (e.g. `"gh": "2.40.0"` → `gh=2.40.0`) — and benefit from BuildKit cache mounts. `cloudflared`, `claudeCode`, and `codexCli` are the exceptions: `cloudflared` downloads the pinned GitHub release, `claudeCode` runs Anthropic's `curl | bash` installer, `codexCli` `bun install`s the `@openai/codex` npm package; all three are boolean-only. Use a toggle whenever it covers what the user wants over a hand-rolled `append` entry.
10
+ 2. **`append`** is the escape hatch for everything the toggles don't cover. An array of single-line Dockerfile instructions spliced in right before `ENTRYPOINT`, prefixed with a `# Custom lines from typeclaw.json#docker.file.append.` comment.
11
+
12
+ | Field | Required | Type | Notes |
13
+ | ------------- | -------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
14
+ | `tmux` | no | boolean \| string | Default `true`. `false` omits tmux from the apt install. String pins the Debian package version (e.g. `"3.3a-3"` → `tmux=3.3a-3`). |
15
+ | `gh` | no | boolean \| string | Default `true`. `false` omits **both** the `gh` package and the GitHub CLI keyring bootstrap layer (skipping the network roundtrip on cold builds). String pins the version. |
16
+ | `python` | no | boolean | Default `true`. Fans out to `python3 python3-pip python3-venv python-is-python3` (the bundle that makes `python` and `pip` resolve correctly inside the container). Boolean-only — no version pin, because Debian's `python3` is a meta-package that doesn't accept a useful pin. |
17
+ | `ffmpeg` | no | boolean \| string | Default `false`. `true` apt-installs ffmpeg (~80 MB of codecs). String pins the version. |
18
+ | `cjkFonts` | no | boolean or `"auto"` | Default `"auto"`. Installs `fonts-noto-cjk` (~89 MB) so Chromium (used by `agent-browser`) renders Korean/Japanese/Chinese glyphs correctly in screenshots, `page.pdf()`, and other raster output. `"auto"` resolves at `typeclaw start` from the host locale (`LANG`/`LC_ALL`/`Intl`): a CJK host (ja/ko/zh) installs the fonts, any other host skips them. An explicit `true`/`false` forces the decision. `false` skips the layer entirely (DOM/innerText scraping is unaffected by font absence — only raster output shows tofu boxes). |
19
+ | `cloudflared` | no | boolean | Default `false`. Downloads the pinned `cloudflared` GitHub release (~38 MB) into the image so `cloudflare-quick` tunnels work. Default `false` skips the layer on agents that don't use tunnels; `typeclaw tunnel add` / `channel add github` with a Cloudflare provider flip it to `true` automatically and prompt for a restart, so the happy path needs no manual edit. If the binary is absent when a tunnel starts, the tunnel goes `permanently-failed` with a "set docker.file.cloudflared: true and run typeclaw restart" message. Boolean-only — pinning is owned by the typeclaw release. |
20
+ | `xvfb` | no | boolean | Default `true`. Installs `xvfb` (~5 MB) so the entrypoint shim can spawn a virtual X server and export `DISPLAY=:99`, giving headed Chrome (agent-browser `--headed`, headful Playwright) a real X11 display to defeat headless-mode WAF fingerprinting. `false` skips the layer; the shim self-heals (no `Xvfb` on PATH → execs the agent without `DISPLAY`). Boolean-only — xvfb tracks the upstream X server release with no useful apt pin. |
21
+ | `claudeCode` | no | boolean | Default `false`. `true` runs Anthropic's official `curl -fsSL https://claude.ai/install.sh \| bash` in a dedicated layer (between agent-browser and the entrypoint shim) and pre-seeds `~/.claude.json` to skip the TTY-only theme picker on first launch (without it the agent's `tmux send-keys` would be eaten by the picker). Not apt: no version-pin variant; the upstream installer manages channels via env vars. Pairs with the `typeclaw-claude-code` skill, which documents the auth + tmux-driven usage flow including how to clear the post-seed API-key/trust dialogs. |
22
+ | `codexCli` | no | boolean | Default `false`. `true` runs `bun install -g @openai/codex` in a dedicated layer (after `claudeCode`, before the entrypoint shim) and pre-writes `~/.codex/hooks.json` registering `SessionStart` + `Stop` hooks so the operator can detect turn boundaries the same way as Claude Code (sentinel files, `.session-id` discovery). Not apt: no version-pin variant. Codex CLI has NO theme picker so no onboarding seed is needed, but auth (`codex login` or `OPENAI_API_KEY`) and the per-project trust dialog are still required at runtime — handled by the `typeclaw-codex-cli` skill. |
23
+ | `append` | no | array of strings | Each entry is a single Dockerfile line — schema **rejects** entries containing `\n` or `\r`. Defaults to `[]`. Splice happens just before `ENTRYPOINT`, after `ENV NODE_ENV=production`. |
24
+
25
+ Toggle version strings reject whitespace and `=` (apt-injection guard) — pass just the version, not `pkg=ver`.
26
+
27
+ ## The single-line constraint (`append` only)
28
+
29
+ Each entry of `append` must be one Dockerfile instruction's worth of source — a `RUN`, `ENV`, `COPY`, `ARG`, etc. The schema enforces "no embedded newlines" because a multiline string in the JSON would silently break Dockerfile syntax (Dockerfile line continuations require backslashes at end-of-line, and a JSON multiline doesn't carry those). If the user wants a logically multi-step instruction, give them two entries:
30
+
31
+ ```json
32
+ "docker": {
33
+ "file": {
34
+ "append": [
35
+ "RUN apt-get update && apt-get install -y --no-install-recommends ripgrep fd-find",
36
+ "ENV CUSTOM_TOOL=1"
37
+ ]
38
+ }
39
+ }
40
+ ```
41
+
42
+ A single `RUN` with `&&`-chained shell commands is fine and idiomatic — that's still a single Dockerfile line. What's rejected is a literal newline inside the JSON string.
43
+
44
+ ## Where things land in the build
45
+
46
+ The template's last layers are roughly:
47
+
48
+ ```
49
+ RUN apt-get install ... <baseline + enabled toggle packages> ← toggles fan out into this line
50
+ ...
51
+ ENV NODE_ENV=production
52
+ # Custom lines from typeclaw.json#docker.file.append. ← only emitted when append is non-empty
53
+ <your appended lines>
54
+ ENTRYPOINT ["/usr/local/bin/typeclaw-entrypoint"]
55
+ CMD ["run"]
56
+ ```
57
+
58
+ The toggle-driven apt install benefits from BuildKit `--mount=type=cache` on `/var/cache/apt` and `/var/lib/apt/lists`, so toggling `ffmpeg: true` (or pinning `gh: "2.40.0"`) only re-fetches what changed. The `gh` keyring bootstrap is in its own earlier layer that's gated on `gh` being enabled — turning `gh: false` saves the network roundtrip even on cold builds.
59
+
60
+ `append` runs after every cache-friendly base layer (apt setup, the toggle-driven apt install, `agent-browser`, Chrome for Testing on amd64), so changing `append` invalidates only the final layer. Conversely, putting `apt-get install` in `append` is **slower than using a toggle** (no BuildKit cache mount) — and if the package you want is `tmux/gh/python/ffmpeg/cjkFonts`, just use the toggle.
61
+
62
+ ## Restart and rebuild semantics
63
+
64
+ - **Restart-required.** `docker.file` is in `FIELD_EFFECTS` as restart-required. `reload` reports the change as `restartRequired` and the live container keeps running on the old image.
65
+ - **The next `typeclaw start` rebuilds the image automatically.** No `--build` flag is needed; the CLI re-runs `docker build` whenever the Dockerfile content has changed (it rewrites the file from the current template + current `docker.file` block every start). Tell the user: "Edited `docker.file` — restart-required. The next `typeclaw start` will rewrite the Dockerfile and rebuild the image."
66
+ - **Pre-existing host-side edits to the Dockerfile are clobbered.** If the user manually edited the Dockerfile before, the next `start` overwrites it and (if the working tree was dirty) auto-commits the cleanup. This is by design; don't try to preserve manual edits.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: typeclaw-cron
3
- description: Use this skill whenever the user asks you to schedule recurring work, run something on a cron, do something every day/hour/week, set up a periodic task, list or inspect scheduled jobs, or read or edit your cron schedule. Triggers include "every morning", "every Monday", "schedule a", "remind me every", "set up a cron", "run X periodically", "list cron jobs", "list scheduled jobs", "show me the cron", "what cron jobs do you have", "what's on my cron", "when does X run", or any mention of `cron.json`. Read it before touching `cron.json` — the file has a strict schema, restart semantics, and a best-effort execution model that you must not misrepresent to the user.
3
+ description: Use this skill whenever the user asks you to schedule recurring work OR a one-off future task/reminder, run something on a cron, do something every day/hour/week, do something once at a future time, set up a periodic task, list or inspect scheduled jobs, or read or edit your cron schedule. Triggers include "every morning", "every Monday", "schedule a", "remind me every", "set up a cron", "run X periodically", "remind me in 3 days", "remind me tomorrow", "remind me at 9am", "remind me next Monday", "in N hours/days do X", "do X once at hh:mm", "stop after N times", "until <date>", "list cron jobs", "list scheduled jobs", "show me the cron", "what cron jobs do you have", "what's on my cron", "when does X run", or any mention of `cron.json`. Read it before touching `cron.json` — the file has a strict schema, restart semantics, and a best-effort execution model that you must not misrepresent to the user.
4
4
  ---
5
5
 
6
6
  # typeclaw-cron
@@ -25,12 +25,17 @@ Tell the user this if they ask about reliability. Do not invent guarantees the r
25
25
 
26
26
  ### Shared fields (all jobs)
27
27
 
28
- | Field | Required | Notes |
29
- | ---------- | -------- | ------------------------------------------------------------------------------------------------------------- |
30
- | `id` | yes | Unique. Letters, digits, hyphens, underscores. Used in logs and to coalesce. |
31
- | `schedule` | yes | Standard 5-field cron expression (`min hr dom mon dow`) or 6-field with seconds. See "Schedule syntax" below. |
32
- | `enabled` | no | Defaults to `true`. Set to `false` to keep a job in the file but skip it. |
33
- | `timezone` | no | IANA name like `Asia/Seoul`. Defaults to UTC (the container's timezone). |
28
+ | Field | Required | Notes |
29
+ | ---------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
30
+ | `id` | yes | Unique. Letters, digits, hyphens, underscores. Used in logs and to coalesce. |
31
+ | `schedule` | one of these | Standard 5-field cron expression (`min hr dom mon dow`) or 6-field with seconds. Recurring. See "Schedule syntax" below. |
32
+ | `at` | one of these | One-shot ISO instant fires **once** then retires. Mutually exclusive with `schedule`; set exactly one. See "One-shot reminders (`at`)" below. |
33
+ | `until` | no | Recurring only. Absolute ISO instant; last allowed fire (inclusive). The job retires after this. |
34
+ | `count` | no | Recurring only. Stop after N accepted fires. Coexists with `until` — whichever boundary is reached first wins. |
35
+ | `enabled` | no | Defaults to `true`. Set to `false` to keep a job in the file but skip it. |
36
+ | `timezone` | no | IANA name like `Asia/Seoul`. Recurring (`schedule`) only — NOT valid with `at`. Defaults to UTC (the container's timezone). |
37
+
38
+ **`schedule` XOR `at`:** every job has exactly one of `schedule` (recurring) or `at` (one-shot). Setting both, or neither, is rejected. `at` jobs may not set `until`, `timezone`, or `count` > 1 (the instant already pins the single fire).
34
39
 
35
40
  ### `kind: "prompt"` — fire a prompt into a fresh session
36
41
 
@@ -69,6 +74,49 @@ Use `exec` only for jobs that are pure mechanics — no judgement required. Exam
69
74
 
70
75
  `command` is an array. Index 0 is the executable, the rest are argv. Do **not** put a single shell pipeline in `command[0]` — that won't be parsed by a shell. If you need shell features (`|`, `>`, `&&`), wrap explicitly: `["sh", "-c", "your | pipeline | here"]`.
71
76
 
77
+ ## One-shot reminders and future tasks (`at`)
78
+
79
+ When the user wants something to happen **once at a future time** — "remind me in 3 days to cancel the subscription", "ping me tomorrow at 9", "in 2 hours, check if the build finished" — use `at` instead of `schedule`. The job fires exactly once at that instant, then retires (the scheduler stops arming it; it never fires again).
80
+
81
+ ```json
82
+ {
83
+ "id": "cancel-sub",
84
+ "at": "2026-06-11T09:00:00+09:00",
85
+ "kind": "prompt",
86
+ "prompt": "Remind the user to cancel the subscription they mentioned on 2026-06-08. If they already handled it, say so and move on.",
87
+ "scheduledByRole": "owner"
88
+ }
89
+ ```
90
+
91
+ `at` works with any `kind` (`prompt` for "remind me / do this judgement task", `exec` for a one-off mechanical command). The same best-effort rules apply: if the container is down at the `at` instant, the fire is **lost, not replayed**. Say so if the user is relying on it for something important.
92
+
93
+ ### The `at` value MUST carry an explicit zone or offset
94
+
95
+ `at` is parsed as an **absolute instant**, so it requires a trailing `Z` or a numeric offset. A bare local-time string is rejected (it would silently resolve to UTC and surprise the user).
96
+
97
+ - ✅ `2026-06-11T09:00:00+09:00` (Seoul morning) or `2026-06-11T00:00:00Z`
98
+ - ❌ `2026-06-11T09:00:00` (no zone — rejected by `parseCronFile`)
99
+
100
+ **The `at` instant must be in the future.** Writing an enabled `at` in the past is **rejected at write time** (the `reload`/guard validation returns `"at" is in the past`), because a past reminder would be retired immediately and never fire — a silent no-op the user would mistake for a scheduled reminder. If you get this error, recompute the instant (you probably botched the timezone offset) and write again. (An already-_fired_ one-shot left on disk is the one exception — it stays valid so it can't brick reload — but you never author one of those by hand.)
101
+
102
+ **Resolving "9am" / "tomorrow" / "in 3 days" to an instant:**
103
+
104
+ 1. Get the user's timezone. Check `USER.md` for a recorded zone; if it's not there and the wall-clock matters, ask once.
105
+ 2. Compute the absolute instant in that zone. "Remind me in 3 days" → take now, add 3×24h (or the next 09:00 in their zone if they said a time), and emit it with the zone's offset, e.g. `+09:00`.
106
+ 3. Use `bash` (`date`) if you need to compute the offset precisely rather than guessing — e.g. `date -u -d '+3 days' +%Y-%m-%dT%H:%M:%SZ`. Don't hand-roll DST math.
107
+
108
+ Do not invent `until`, `timezone`, or `count` > 1 on an `at` job — they're rejected. The single instant is the whole schedule.
109
+
110
+ ### Clean up after a one-shot fires (self-prune)
111
+
112
+ A fired `at` job does **not** delete itself from `cron.json` — it stays on disk as an inert, already-retired entry (the runtime never writes back to `cron.json`; that's by design). On its own this is harmless: the scheduler sees the past instant and retires it without firing, so it will not run again and will not break reload.
113
+
114
+ But to keep `cron.json` clean, **a one-shot `prompt` job should remove its own entry as the last step of its fire.** Because the fire runs in a full agent session with all your tools, end the reminder prompt with a self-cleanup instruction. Write your `prompt` so your future self does this:
115
+
116
+ > "... After delivering the reminder, remove the cron job with id `cancel-sub` from `cron.json` and call the `reload` tool so the dead one-shot doesn't linger."
117
+
118
+ Removing a job needs **no** `cronPromotion` ack — deletions pass the guard freely (only _adding_ a job or elevating its role requires `acknowledgeGuards`). If the cleanup is ever skipped (model error, crash mid-session), the worst case is a harmless leftover entry you can prune on any later edit — never a broken schedule. This self-prune only applies to `prompt` jobs; an `at` `exec` job has no LLM to clean up after itself, so its entry just lingers until a human or a later prompt removes it.
119
+
72
120
  ## `exec → LLM`: write a plugin cron handler (best practice)
73
121
 
74
122
  If a scheduled job needs imperative control flow that mixes shell calls and LLM calls (probe → maybe prompt → write file), the best practice is a **plugin cron handler**: a TypeScript function the plugin registers under its own `cronJobs` with `kind: 'handler'`. The cron consumer invokes it directly — no shell-out, no WS round-trip, no `Bun.spawn`. Prefer this whenever the cadence and the logic both belong to the same plugin (which is almost always — see "When to reach for the exec bridge instead" below for the two narrow exceptions).
@@ -322,11 +370,13 @@ For every job you add:
322
370
 
323
371
  - `id` is unique within the file
324
372
  - `id` matches `[a-zA-Z0-9_-]+` (no spaces, no slashes, no dots)
325
- - `schedule` parses as cron
373
+ - Exactly one of `schedule` or `at` is set (never both, never neither)
374
+ - If recurring: `schedule` parses as cron
375
+ - If one-shot: `at` is a future ISO instant **with an explicit `Z` or numeric offset**, and `until`/`timezone`/`count` > 1 are absent
326
376
  - `kind` is exactly `"prompt"` or `"exec"`
327
377
  - If `prompt`: `prompt` is non-empty
328
378
  - If `exec`: `command` is a non-empty array of non-empty strings
329
- - If a wall-clock schedule was requested: `timezone` is set
379
+ - If a wall-clock `schedule` was requested: `timezone` is set
330
380
 
331
381
  ### Applying changes — the `reload` tool
332
382
 
@@ -345,19 +395,23 @@ If you finished an edit and the user only sees an in-flight job from the previou
345
395
  - **Do not promise sub-second precision or guaranteed execution.** This is best-effort — see "What cron actually does" above.
346
396
  - **Do not invent fields the schema doesn't support** (no `retry`, `timeout`, `onFailure`, `concurrency`, etc.). They will be silently ignored at best, or rejected at worst.
347
397
 
348
- ## When the user says "every X"
398
+ ## When the user says "every X" or "do X once"
399
+
400
+ 0. **Recurring or one-shot?** This is the first fork.
401
+ - **Recurring** ("every morning", "every Monday", "hourly") → use `schedule`. Continue with step 1 below.
402
+ - **One-shot / future task** ("remind me in 3 days", "tomorrow at 9", "in 2 hours", "do X once at hh:mm") → use `at` with an absolute instant (explicit zone/offset). See "One-shot reminders and future tasks (`at`)" above for resolving the instant and self-prune. Then pick the kind (almost always `prompt` for a reminder) and skip straight to step 4. Don't set `schedule`, `timezone`, `until`, or `count` on it.
349
403
 
350
- Pick `kind` first, then schedule, then timezone:
404
+ For a **recurring** job:
351
405
 
352
406
  1. **Pick the kind.**
353
407
  - **Pure mechanics, no judgement** (git snapshots, log rotation, calling an existing script) → `kind: 'exec'` in `cron.json`. Done.
354
- - **One-shot natural-language instruction, no shell pre-work, no conditional logic** → `kind: 'prompt'` in `cron.json`. Done.
408
+ - **One natural-language instruction, no shell pre-work, no conditional logic** → `kind: 'prompt'` in `cron.json`. Done.
355
409
  - **Imperative control flow mixing shell calls and LLM calls** (probe → maybe prompt → write file, "if there are new emails then triage", etc.) → **write a `kind: 'handler'` plugin cron job** (see "`exec → LLM`: write a plugin cron handler" above). This is the default for scheduled `exec → LLM` work.
356
410
  - **Reuse a CLI command on a custom cadence** — the same logic must ALSO be invocable from the TUI / manual shell / `compose` orchestration, or the schedule is owned by the user (`cron.json`) rather than the plugin author, or the work must run as a `surface: 'host'` command → `kind: 'exec'` in `cron.json` with `command: ["typeclaw", "<plugin-command>", ...]`. Reach for this ONLY when reusability is the actual requirement, not just because the work is scheduled. See "When to reach for the exec bridge instead" above.
357
- 2. **Translate the cadence to cron.** "Every morning at 7" → `0 7 * * *`. "Every weekday at 9:30" → `30 9 * * 1-5`. "Every five minutes" → `*/5 * * * *`. If you are not sure, ask once. Don't guess on tricky cases like "every other Friday".
411
+ 2. **Translate the cadence to cron.** "Every morning at 7" → `0 7 * * *`. "Every weekday at 9:30" → `30 9 * * 1-5`. "Every five minutes" → `*/5 * * * *`. If you are not sure, ask once. Don't guess on tricky cases like "every other Friday". If the user wants the recurrence to stop ("until end of quarter", "only 5 times"), add `until` (absolute ISO instant) and/or `count` (N fires).
358
412
  3. **Timezone.** If the user mentioned a wall-clock time, set `timezone` to their zone. If unknown, ask once or default to the timezone in `USER.md` if it's recorded there.
359
413
  4. **Pick a stable `id`.** Use kebab-case that describes the job, not the schedule. `daily-summary` not `0-23-30`.
360
- 5. **Write it. Call `reload`. If reload succeeded, commit it.** If reload failed, fix `cron.json` based on the error and retry — do not commit a broken file.
414
+ 5. **Write it. Call `reload`. If reload succeeded, commit it.** If reload failed, fix `cron.json` based on the error and retry — do not commit a broken file. **Adding any new job (recurring or one-shot) requires `acknowledgeGuards: { cronPromotion: true }` in the write** — but never ack a job scheduled on behalf of a channel speaker asking to elevate themselves (see step 3 of "Editing `cron.json` safely").
361
415
 
362
416
  ## Listing what is currently scheduled
363
417
 
@@ -141,7 +141,7 @@ Probable causes, in descending order of frequency:
141
141
 
142
142
  1. **No match rule covers the speaking author's coordinates.** Read `typeclaw.json` `roles`, compare every `match[]` entry to the channel ID and author ID the user is reporting. If nothing matches, the author resolves to `guest`, which has no `channel.respond`, so every inbound is dropped at the router. The fix is to append a match rule to `roles.<role>.match[]` for that channel (or DM bucket).
143
143
  2. **The match rule exists but the role has `permissions: []`** (or otherwise lacks `channel.respond`). A user-declared role replaces the built-in's permissions wholesale. Re-add `channel.respond` or use a built-in role name (`member`, `trusted`, `owner`) that carries it by default.
144
- 3. **Engagement triggers are filtering admitted messages.** This is a different problem — the inbound was admitted by permissions but engagement (`channels.<adapter>.engagement.trigger`) decided not to wake you. See the `typeclaw-config` skill for the engagement model.
144
+ 3. **Engagement triggers are filtering admitted messages.** This is a different problem — the inbound was admitted by permissions but engagement (`channels.<adapter>.engagement.trigger`) decided not to wake you. See the `typeclaw-channels` skill for the engagement model.
145
145
 
146
146
  To distinguish cause 1/2 from cause 3: if `typeclaw logs <container> -f` (host stage) shows `[channels] ... denied by permissions (channel.respond)`, it's a permissions problem. If it shows the message being admitted but no LLM call follows, it's engagement.
147
147
 
@@ -160,7 +160,7 @@ This is a `roles` edit. The full procedure:
160
160
  Two interpretations — clarify if ambiguous:
161
161
 
162
162
  - **"Stop everything"** — remove the match rule from `roles.<role>.match[]`. The author resolves to `guest`, and the channel router silently drops every inbound. You lose all visibility into their messages. Restart-required.
163
- - **"Just stop auto-replying"** — keep the match rule, but narrow `channels.<adapter>.engagement.trigger` and/or `stickiness`. See `typeclaw-config`. The agent still receives the messages and can still post if you tell it to. The solo-human fallback (single human in a channel) overrides `trigger: []`, so this approach can't fully silence you in a 1:1; only removing the match rule does.
163
+ - **"Just stop auto-replying"** — keep the match rule, but narrow `channels.<adapter>.engagement.trigger` and/or `stickiness`. See `typeclaw-channels`. The agent still receives the messages and can still post if you tell it to. The solo-human fallback (single human in a channel) overrides `trigger: []`, so this approach can't fully silence you in a 1:1; only removing the match rule does.
164
164
 
165
165
  ## When the user asks "what role am I in this session?"
166
166
 
@@ -192,7 +192,7 @@ If you see a cron job mysteriously failing every fire with `denied by permission
192
192
 
193
193
  ## What this skill does not cover
194
194
 
195
- - **The `channels.<adapter>` block** — engagement, history, stickiness, alias. See `typeclaw-config`. Engagement decides whether an _admitted_ inbound wakes the loop; this skill is only about admission.
195
+ - **The `channels.<adapter>` block behavior** — engagement, history, stickiness, alias matching. See `typeclaw-channels`. Engagement decides whether an _admitted_ inbound wakes the loop; this skill is only about admission. The `channels`/`alias` schema and edit mechanics live in `typeclaw-config`.
196
196
  - **The full `typeclaw.json` schema** — model, mounts, plugins, docker, git.ignore. See `typeclaw-config`.
197
197
  - **Cron job authoring** — schedule syntax, `prompt` vs `exec`, the `reload` tool. See `typeclaw-cron`. This skill only covers the `scheduledByRole` field and its provenance semantics.
198
198
  - **Plugin authoring** — `definePlugin`, contributing permissions, custom `tool.before` hooks. See `typeclaw-plugins`. The bundled security plugin is an example of a plugin that contributes `security.bypass.*` strings and uses `permissions.has()` to gate its own guards.
@@ -41,7 +41,18 @@
41
41
  "zai-coding/glm-4.7",
42
42
  "zai-coding/glm-5",
43
43
  "zai-coding/glm-5-turbo",
44
- "zai-coding/glm-5.1"
44
+ "zai-coding/glm-5.1",
45
+ "xai/grok-4.3",
46
+ "xai/grok-4.20-0309-reasoning",
47
+ "xai/grok-4.20-0309-non-reasoning",
48
+ "xai/grok-build-0.1",
49
+ "minimax/MiniMax-M3",
50
+ "minimax/MiniMax-M2.7",
51
+ "minimax/MiniMax-M2.5",
52
+ "minimax/MiniMax-M2.1",
53
+ "minimax/MiniMax-M2",
54
+ "deepseek/deepseek-v4-flash",
55
+ "deepseek/deepseek-v4-pro"
45
56
  ]
46
57
  },
47
58
  {
@@ -69,7 +80,18 @@
69
80
  "zai-coding/glm-4.7",
70
81
  "zai-coding/glm-5",
71
82
  "zai-coding/glm-5-turbo",
72
- "zai-coding/glm-5.1"
83
+ "zai-coding/glm-5.1",
84
+ "xai/grok-4.3",
85
+ "xai/grok-4.20-0309-reasoning",
86
+ "xai/grok-4.20-0309-non-reasoning",
87
+ "xai/grok-build-0.1",
88
+ "minimax/MiniMax-M3",
89
+ "minimax/MiniMax-M2.7",
90
+ "minimax/MiniMax-M2.5",
91
+ "minimax/MiniMax-M2.1",
92
+ "minimax/MiniMax-M2",
93
+ "deepseek/deepseek-v4-flash",
94
+ "deepseek/deepseek-v4-pro"
73
95
  ]
74
96
  }
75
97
  }
@@ -584,6 +606,166 @@
584
606
  }
585
607
  }
586
608
  },
609
+ "line": {
610
+ "type": "object",
611
+ "properties": {
612
+ "engagement": {
613
+ "default": {
614
+ "trigger": [
615
+ "mention",
616
+ "reply",
617
+ "dm"
618
+ ],
619
+ "stickiness": {
620
+ "perReply": {
621
+ "window": 900000
622
+ }
623
+ }
624
+ },
625
+ "type": "object",
626
+ "properties": {
627
+ "trigger": {
628
+ "default": [
629
+ "mention",
630
+ "reply",
631
+ "dm"
632
+ ],
633
+ "type": "array",
634
+ "items": {
635
+ "type": "string",
636
+ "enum": [
637
+ "mention",
638
+ "reply",
639
+ "dm"
640
+ ]
641
+ }
642
+ },
643
+ "stickiness": {
644
+ "default": {
645
+ "perReply": {
646
+ "window": 900000
647
+ }
648
+ },
649
+ "anyOf": [
650
+ {
651
+ "type": "string",
652
+ "const": "off"
653
+ },
654
+ {
655
+ "type": "object",
656
+ "properties": {
657
+ "perReply": {
658
+ "type": "object",
659
+ "properties": {
660
+ "window": {
661
+ "type": "integer",
662
+ "minimum": 1,
663
+ "maximum": 86400000
664
+ }
665
+ },
666
+ "required": [
667
+ "window"
668
+ ]
669
+ }
670
+ },
671
+ "required": [
672
+ "perReply"
673
+ ]
674
+ }
675
+ ]
676
+ }
677
+ }
678
+ },
679
+ "history": {
680
+ "default": {
681
+ "prefetch": {
682
+ "thread": {
683
+ "head": 3,
684
+ "tail": 10
685
+ },
686
+ "channel": {
687
+ "tail": 10
688
+ }
689
+ }
690
+ },
691
+ "type": "object",
692
+ "properties": {
693
+ "prefetch": {
694
+ "default": {
695
+ "thread": {
696
+ "head": 3,
697
+ "tail": 10
698
+ },
699
+ "channel": {
700
+ "tail": 10
701
+ }
702
+ },
703
+ "type": "object",
704
+ "properties": {
705
+ "thread": {
706
+ "default": {
707
+ "head": 3,
708
+ "tail": 10
709
+ },
710
+ "type": "object",
711
+ "properties": {
712
+ "head": {
713
+ "default": 3,
714
+ "type": "integer",
715
+ "minimum": 0,
716
+ "maximum": 200
717
+ },
718
+ "tail": {
719
+ "default": 10,
720
+ "type": "integer",
721
+ "minimum": 0,
722
+ "maximum": 200
723
+ }
724
+ }
725
+ },
726
+ "channel": {
727
+ "default": {
728
+ "tail": 10
729
+ },
730
+ "type": "object",
731
+ "properties": {
732
+ "tail": {
733
+ "default": 10,
734
+ "type": "integer",
735
+ "minimum": 0,
736
+ "maximum": 200
737
+ }
738
+ }
739
+ }
740
+ }
741
+ }
742
+ }
743
+ },
744
+ "enabled": {
745
+ "default": true,
746
+ "type": "boolean"
747
+ },
748
+ "quotedReply": {
749
+ "default": {
750
+ "enabled": true,
751
+ "queueDelayMs": 10000
752
+ },
753
+ "type": "object",
754
+ "properties": {
755
+ "enabled": {
756
+ "default": true,
757
+ "type": "boolean"
758
+ },
759
+ "queueDelayMs": {
760
+ "default": 10000,
761
+ "type": "integer",
762
+ "minimum": 0,
763
+ "maximum": 9007199254740991
764
+ }
765
+ }
766
+ }
767
+ }
768
+ },
587
769
  "kakaotalk": {
588
770
  "type": "object",
589
771
  "properties": {
@@ -1286,7 +1468,7 @@
1286
1468
  "type": "array",
1287
1469
  "items": {
1288
1470
  "type": "string",
1289
- "pattern": "^(tui|cron|subagent(:[a-z][a-z0-9-]*)?|\\*|(slack|discord|telegram|kakao|github):[^\\s]+)(\\s+[a-zA-Z][a-zA-Z0-9_]*:[^\\s]+)*$"
1471
+ "pattern": "^(tui|cron|subagent(:[a-z][a-z0-9-]*)?|\\*|(slack|discord|telegram|line|kakao|github):[^\\s]+)(\\s+[a-zA-Z][a-zA-Z0-9_]*:[^\\s]+)*$"
1290
1472
  }
1291
1473
  },
1292
1474
  "permissions": {