typeclaw 0.31.1 → 0.32.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/package.json
CHANGED
|
@@ -129,14 +129,34 @@ type PlatformInfo = {
|
|
|
129
129
|
// the call would no-op. Keep in sync with the adapters that call
|
|
130
130
|
// `router.registerReaction` (github, slack-bot, discord-bot today).
|
|
131
131
|
supportsReactions: boolean
|
|
132
|
+
// Whether this adapter's OutboundCallback accepts file attachments. Gates the
|
|
133
|
+
// "ship a researcher report as a PDF by default" prompt guidance: a report is
|
|
134
|
+
// only worth converting to a downloadable file on channels that can actually
|
|
135
|
+
// receive one. GitHub's outbound callback hard-rejects attachments
|
|
136
|
+
// (`github-bot-does-not-support-attachments` in adapters/github/outbound.ts),
|
|
137
|
+
// so a PDF nudge there would train the model toward a call that always fails;
|
|
138
|
+
// the other four upload files (Slack `uploadFile`, Discord `uploadFile`,
|
|
139
|
+
// Telegram `sendDocument`, KakaoTalk `sendAttachment`). Keep in sync with the
|
|
140
|
+
// adapters' outbound callbacks.
|
|
141
|
+
supportsAttachments: boolean
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
const PLATFORM_INFO: Record<AdapterId, PlatformInfo> = {
|
|
135
|
-
'slack-bot': { displayName: 'Slack', mentionMode: 'angle-id', supportsReactions: true },
|
|
136
|
-
'discord-bot': {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
'slack-bot': { displayName: 'Slack', mentionMode: 'angle-id', supportsReactions: true, supportsAttachments: true },
|
|
146
|
+
'discord-bot': {
|
|
147
|
+
displayName: 'Discord',
|
|
148
|
+
mentionMode: 'angle-id',
|
|
149
|
+
supportsReactions: true,
|
|
150
|
+
supportsAttachments: true,
|
|
151
|
+
},
|
|
152
|
+
github: { displayName: 'GitHub', mentionMode: 'at-username', supportsReactions: true, supportsAttachments: false },
|
|
153
|
+
'telegram-bot': {
|
|
154
|
+
displayName: 'Telegram',
|
|
155
|
+
mentionMode: 'at-username',
|
|
156
|
+
supportsReactions: false,
|
|
157
|
+
supportsAttachments: true,
|
|
158
|
+
},
|
|
159
|
+
kakaotalk: { displayName: 'KakaoTalk', mentionMode: 'alias', supportsReactions: false, supportsAttachments: true },
|
|
140
160
|
}
|
|
141
161
|
|
|
142
162
|
function getPlatformInfo(adapter: AdapterId): PlatformInfo {
|
|
@@ -461,6 +481,7 @@ function renderChannelOrigin(
|
|
|
461
481
|
"matching the channel's `allow` rules are accepted (the tool returns",
|
|
462
482
|
'`{ ok: false }` otherwise).',
|
|
463
483
|
'',
|
|
484
|
+
...renderResearchReportDeliveryGuidance(platformInfo),
|
|
464
485
|
...renderMentionGuidance(platformInfo, origin.participants ?? [], now, origin.self),
|
|
465
486
|
)
|
|
466
487
|
|
|
@@ -496,6 +517,30 @@ function renderMembershipSummary(
|
|
|
496
517
|
return `This channel has approximately ${total} members (about ${membership.humans} humans, ${membership.bots} bots — the bot count is approximate, the full member list was not enumerated because it exceeds the 50-member cap). The 10 most recent speakers are listed below.`
|
|
497
518
|
}
|
|
498
519
|
|
|
520
|
+
// The `researcher` subagent always hands back a markdown report file
|
|
521
|
+
// (`research-<slug>.md`) and is itself read-only — it cannot produce the PDF.
|
|
522
|
+
// Whoever delivers that report to a channel is the one who decides the format,
|
|
523
|
+
// and on a channel that accepts file uploads the right default for a multi-page
|
|
524
|
+
// research report is a downloadable PDF, not a wall of raw markdown dumped into
|
|
525
|
+
// chat. This block makes that the default ONLY where it is actionable: gated on
|
|
526
|
+
// `supportsAttachments` so GitHub (whose outbound callback rejects attachments)
|
|
527
|
+
// never gets a nudge toward a `channel_send` call that would fail.
|
|
528
|
+
function renderResearchReportDeliveryGuidance(platformInfo: PlatformInfo): string[] {
|
|
529
|
+
if (!platformInfo.supportsAttachments) return []
|
|
530
|
+
return [
|
|
531
|
+
`**Ship \`researcher\` reports as a PDF by default.** ${platformInfo.displayName} accepts file`,
|
|
532
|
+
'attachments. When you receive a `researcher` subagent result — a',
|
|
533
|
+
'`research-<slug>.md` report file path in its `<report>` block — convert that',
|
|
534
|
+
'markdown to a PDF with the `typeclaw-markdown-pdf` skill and deliver it with',
|
|
535
|
+
'`channel_send({ ..., attachments: [{ path, filename }] })`, with a one- or',
|
|
536
|
+
'two-line summary as the message text. A downloadable file is what a human',
|
|
537
|
+
'wants for a multi-page report; do not paste the full markdown into chat. Send',
|
|
538
|
+
'the report inline as plain text only if the caller explicitly asked for it in',
|
|
539
|
+
'the message, or the report is short enough that a file would be overkill.',
|
|
540
|
+
'',
|
|
541
|
+
]
|
|
542
|
+
}
|
|
543
|
+
|
|
499
544
|
function renderMentionGuidance(
|
|
500
545
|
platformInfo: PlatformInfo,
|
|
501
546
|
participants: readonly ChannelParticipant[],
|
|
@@ -108,7 +108,7 @@ fonts/margins only if the user asks.
|
|
|
108
108
|
#counter(page).display("1 / 1", both: true)
|
|
109
109
|
]),
|
|
110
110
|
)
|
|
111
|
-
#set text(font: ("Libertinus Serif", "New Computer Modern"), size: 11pt, lang: "en")
|
|
111
|
+
#set text(font: ("Libertinus Serif", "New Computer Modern", "Noto Serif CJK KR"), size: 11pt, lang: "en")
|
|
112
112
|
#set par(justify: true, leading: 0.68em, spacing: 1.1em)
|
|
113
113
|
|
|
114
114
|
#show heading: set text(weight: "semibold")
|
|
@@ -136,9 +136,15 @@ Notes:
|
|
|
136
136
|
- `read("report.md")` is **relative to the workspace** (the compiler's `workspace`
|
|
137
137
|
is set to `workspace/` — see Step 3). Keep the `.typ` and `.md` in `workspace/`.
|
|
138
138
|
- Fonts `Libertinus Serif` / `New Computer Modern` are bundled with Typst (no font
|
|
139
|
-
install)
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
install) and carry the Latin text. `"Noto Serif CJK KR"` is appended as the
|
|
140
|
+
fallback so Korean/CJK glyphs resolve per-glyph — Typst falls through to it
|
|
141
|
+
wherever the Latin fonts have no glyph, leaving Latin runs untouched. It comes
|
|
142
|
+
from `fonts-noto-cjk`, which Step 3's renderer loads from `/usr/share/fonts` via
|
|
143
|
+
`fontPaths`. **The package is only present when the container's `cjkFonts` toggle
|
|
144
|
+
resolves to `true`.** Its default is `"auto"`, which installs the fonts only when
|
|
145
|
+
the host locale is CJK (`ja`/`ko`/`zh`) — so on a non-CJK host, CJK PDFs still
|
|
146
|
+
render as tofu until you set `docker.file.cjkFonts: true` in `typeclaw.json` and
|
|
147
|
+
rebuild. If your CJK font lives elsewhere, add its dir to the `fontPaths` list.
|
|
142
148
|
|
|
143
149
|
## Step 3 — render
|
|
144
150
|
|
|
@@ -149,15 +155,23 @@ writes the PDF. Pass the wrapper and output paths as arguments.
|
|
|
149
155
|
```ts
|
|
150
156
|
// workspace/.tools/render.ts
|
|
151
157
|
import { NodeCompiler } from '@myriaddreamin/typst-ts-node-compiler'
|
|
152
|
-
import { writeFileSync } from 'node:fs'
|
|
158
|
+
import { existsSync, writeFileSync } from 'node:fs'
|
|
153
159
|
|
|
154
160
|
const [, , mainFile, outFile] = process.argv
|
|
155
161
|
if (!mainFile || !outFile) throw new Error('usage: render.ts <main.typ> <out.pdf>')
|
|
156
162
|
|
|
163
|
+
// Load system fonts so CJK glyphs resolve. The compiler does NOT auto-discover
|
|
164
|
+
// system font dirs the way the Typst CLI does — without explicit fontPaths,
|
|
165
|
+
// "Noto Serif CJK KR" (from fonts-noto-cjk under /usr/share/fonts) is invisible
|
|
166
|
+
// and Korean/Japanese/Chinese text renders as .notdef tofu boxes. Filtered with
|
|
167
|
+
// existsSync so a missing dir (e.g. on a dev/host run) is skipped, not fatal.
|
|
168
|
+
const fontPaths = ['/usr/share/fonts', '/usr/local/share/fonts', '/Library/Fonts', '/System/Library/Fonts'].filter(
|
|
169
|
+
existsSync,
|
|
170
|
+
)
|
|
171
|
+
|
|
157
172
|
const compiler = NodeCompiler.create({
|
|
158
173
|
workspace: '.', // run from workspace/, so read("report.md") resolves
|
|
159
|
-
|
|
160
|
-
// fontArgs: [{ fontPaths: ["/usr/share/fonts"] }],
|
|
174
|
+
...(fontPaths.length > 0 ? { fontArgs: [{ fontPaths }] } : {}),
|
|
161
175
|
})
|
|
162
176
|
const pdf = compiler.pdf({ mainFilePath: mainFile })
|
|
163
177
|
writeFileSync(outFile, Buffer.from(pdf))
|