typeclaw 0.37.3 → 0.37.5
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 +69 -46
- package/package.json +1 -1
- package/src/agent/compaction.ts +24 -15
- package/src/agent/doctor.ts +6 -1
- package/src/agent/session-origin.ts +101 -173
- package/src/agent/subagents.ts +146 -14
- package/src/agent/system-prompt.ts +46 -48
- package/src/agent/todo/scope.ts +4 -2
- package/src/agent/tools/channel-reply.ts +7 -9
- package/src/bundled-plugins/memory/index.ts +33 -33
- package/src/bundled-plugins/memory/load-memory.ts +92 -35
- package/src/bundled-plugins/memory/slug.ts +19 -0
- package/src/bundled-plugins/memory/turn-dedup.ts +32 -29
- package/src/bundled-plugins/security/policies/private-surface-read.ts +4 -1
- package/src/bundled-plugins/tool-result-cap/README.md +7 -7
- package/src/bundled-plugins/tool-result-cap/index.ts +1 -1
- package/src/channels/adapters/discord-bot.ts +11 -4
- package/src/channels/adapters/github/inbound.ts +68 -43
- package/src/channels/adapters/github/index.ts +57 -9
- package/src/channels/adapters/github/recover-failed-deliveries.ts +270 -0
- package/src/channels/adapters/kakaotalk.ts +5 -1
- package/src/channels/adapters/mention-hints.ts +75 -0
- package/src/channels/adapters/slack-bot.ts +8 -2
- package/src/channels/continuation-willingness.ts +216 -68
- package/src/channels/router.ts +149 -15
- package/src/cli/dreams.ts +2 -2
- package/src/cli/init.ts +41 -7
- package/src/cli/inspect.ts +2 -2
- package/src/cli/logs.ts +2 -2
- package/src/cli/qr.ts +4 -3
- package/src/cli/require-agent-dir.ts +31 -0
- package/src/cli/shell.ts +2 -2
- package/src/cli/stop.ts +2 -2
- package/src/cli/tui.ts +20 -6
- package/src/cli/ui.ts +8 -4
- package/src/container/shared.ts +18 -0
- package/src/container/start.ts +1 -1
- package/src/doctor/checks.ts +145 -2
- package/src/hostd/client.ts +48 -52
- package/src/hostd/daemon.ts +82 -39
- package/src/hostd/paths.ts +22 -2
- package/src/hostd/spawn.ts +7 -0
- package/src/hostd/tailscale.ts +12 -1
- package/src/init/index.ts +35 -8
- package/src/init/kakaotalk-auth.ts +2 -2
- package/src/init/packagejson.ts +2 -2
- package/src/init/run-bun-install.ts +71 -37
- package/src/inspect/transcript-view.ts +15 -2
- package/src/plugin/loader.ts +7 -4
- package/src/portbroker/hostd-client.ts +32 -6
- package/src/sandbox/session-tmp.ts +6 -1
- package/src/secrets/export-claude-credentials-file.ts +2 -2
- package/src/shared/index.ts +4 -0
- package/src/shared/platform.ts +11 -0
- package/src/shared/wsl.ts +139 -0
- package/src/tui/index.ts +26 -8
- package/src/tui/terminal-guard.ts +139 -0
- package/typeclaw.schema.json +2 -2
|
@@ -232,17 +232,13 @@ function renderChannelRolePolicy(): string {
|
|
|
232
232
|
return [
|
|
233
233
|
'## Your role in this session',
|
|
234
234
|
'',
|
|
235
|
-
'
|
|
236
|
-
'
|
|
237
|
-
'
|
|
238
|
-
'`<your-role>` tag; that per-turn role is authoritative for the current',
|
|
239
|
-
'message and overrides any role implied by session-opening context. An absent',
|
|
240
|
-
'`<your-role>` tag means the current speaker is the unconstrained default.',
|
|
235
|
+
'Channel sessions may include multiple speakers; never carry one speaker’s',
|
|
236
|
+
'role onto later messages. The current speaker’s authoritative role is the',
|
|
237
|
+
'`<your-role>` turn tag; absent tag = unconstrained default.',
|
|
241
238
|
'',
|
|
242
|
-
'Tool calls
|
|
243
|
-
'
|
|
244
|
-
'
|
|
245
|
-
'`typeclaw-permissions` skill for what each role can do.',
|
|
239
|
+
'Tool calls/channel admission are gated by that speaker’s permissions;',
|
|
240
|
+
'`blocked:` or "denied by permissions" means they lack the needed grant.',
|
|
241
|
+
'See `typeclaw-permissions` for role details.',
|
|
246
242
|
].join('\n')
|
|
247
243
|
}
|
|
248
244
|
|
|
@@ -345,9 +341,8 @@ function renderChannelOrigin(
|
|
|
345
341
|
const lines: string[] = [
|
|
346
342
|
'## Session origin',
|
|
347
343
|
'',
|
|
348
|
-
`You are responding inside a ${platformInfo.displayName} channel session.
|
|
349
|
-
'
|
|
350
|
-
'is a tool call. Plain-text output is invisible.',
|
|
344
|
+
`You are responding inside a ${platformInfo.displayName} channel session. No human watches`,
|
|
345
|
+
'a console here; communicate by tool call. Plain-text output is invisible.',
|
|
351
346
|
]
|
|
352
347
|
|
|
353
348
|
// GitHub has no separate "chat" surface — channel_reply IS a public comment
|
|
@@ -357,30 +352,20 @@ function renderChannelOrigin(
|
|
|
357
352
|
if (origin.adapter === 'github') {
|
|
358
353
|
lines.push(
|
|
359
354
|
'',
|
|
360
|
-
'**`channel_reply` posts a public comment directly on this PR/issue.**
|
|
361
|
-
'
|
|
362
|
-
'
|
|
363
|
-
'audience: post the answer (or review summary) itself, never a status',
|
|
364
|
-
'line about having posted it elsewhere. A narrated "Posted review result',
|
|
365
|
-
'for PR #N: …" inside the PR is exactly the failure to avoid.',
|
|
355
|
+
'**`channel_reply` posts a public comment directly on this PR/issue.**',
|
|
356
|
+
'Write for that public audience: the answer/review summary itself, never',
|
|
357
|
+
'operator status like "Posted review result for PR #N: …".',
|
|
366
358
|
'',
|
|
367
359
|
'**Do not post an "On it" acknowledgment comment.** The runtime already',
|
|
368
|
-
'adds an :eyes: reaction
|
|
369
|
-
'
|
|
370
|
-
'want to signal acknowledgment explicitly, use `channel_react({ emoji })`',
|
|
371
|
-
'(it reacts, it does not comment) — never a text ack. Reserve `channel_reply`',
|
|
372
|
-
'for the actual substantive answer.',
|
|
360
|
+
'adds an :eyes: reaction on engage. For explicit ack use `channel_react`;',
|
|
361
|
+
'reserve `channel_reply` for the substantive answer.',
|
|
373
362
|
'',
|
|
374
363
|
'**A formal review verdict already IS the comment — never post it twice.**',
|
|
375
|
-
'
|
|
376
|
-
'
|
|
377
|
-
'
|
|
378
|
-
'
|
|
379
|
-
'
|
|
380
|
-
'that is a visible duplicate, the exact same words landing twice seconds',
|
|
381
|
-
'apart. One verdict, one surface. If you have already submitted the review,',
|
|
382
|
-
'the PR has heard you — `skip_response({ reason: "verdict posted as review" })`',
|
|
383
|
-
'instead of echoing it as a comment.',
|
|
364
|
+
'A PR review (`APPROVE`, `REQUEST_CHANGES`, or `COMMENT`) renders as a PR',
|
|
365
|
+
'comment. Put the verdict/praise/findings in that body, then do NOT echo',
|
|
366
|
+
'it via `channel_reply`/`gh pr comment`; that is a visible duplicate.',
|
|
367
|
+
'One verdict, one surface. After submitting the review, use',
|
|
368
|
+
'`skip_response({ reason: "verdict posted as review" })`.',
|
|
384
369
|
)
|
|
385
370
|
}
|
|
386
371
|
|
|
@@ -395,13 +380,9 @@ function renderChannelOrigin(
|
|
|
395
380
|
lines.push(
|
|
396
381
|
'',
|
|
397
382
|
'**Emit Markdown tables as bare `| a | b |` blocks — never inside a code',
|
|
398
|
-
'fence.** Discord
|
|
399
|
-
'
|
|
400
|
-
'
|
|
401
|
-
'reformatting only fires on raw Markdown: the moment you wrap the table in',
|
|
402
|
-
'a ``` or ~~~ fence it is treated as literal text and lands as ragged pipes.',
|
|
403
|
-
'So write the table directly in your reply with no surrounding fence. Use',
|
|
404
|
-
'fences only for actual code or output you want shown verbatim.',
|
|
383
|
+
'fence.** Discord lacks table rendering; this session auto-reformats raw',
|
|
384
|
+
'pipe tables into aligned columns, but fenced ```/~~~ tables stay literal.',
|
|
385
|
+
'Use fences only for code/output meant to be verbatim.',
|
|
405
386
|
)
|
|
406
387
|
}
|
|
407
388
|
|
|
@@ -418,117 +399,85 @@ function renderChannelOrigin(
|
|
|
418
399
|
if (platformInfo.supportsReactions && origin.reactionRef !== undefined) {
|
|
419
400
|
lines.push(
|
|
420
401
|
'',
|
|
421
|
-
'**React like a teammate would.**
|
|
422
|
-
'
|
|
423
|
-
'
|
|
424
|
-
'
|
|
425
|
-
'
|
|
426
|
-
'funny, `eyes` to signal you are looking. Reach for it when a reaction adds',
|
|
427
|
-
'real warmth or signal — not on every message, and not just because you can.',
|
|
428
|
-
'A reaction does NOT satisfy the reply obligation below: when the message',
|
|
429
|
-
'needs a substantive answer, still send it via `channel_reply`. Think of',
|
|
430
|
-
'reactions as the lightweight, human layer on top of your words, not a',
|
|
431
|
-
'replacement for them.',
|
|
402
|
+
'**React like a teammate would.** `channel_react({ emoji })` adds only a',
|
|
403
|
+
'reaction: `+1` approve, `rocket` shipping/exciting, `tada` celebrate,',
|
|
404
|
+
'`heart` appreciate, `laugh` funny, `eyes` looking. Use it when it adds',
|
|
405
|
+
'real signal, not every turn. It does NOT satisfy the reply obligation;',
|
|
406
|
+
'substantive answers still go through `channel_reply`.',
|
|
432
407
|
)
|
|
433
408
|
}
|
|
434
409
|
|
|
435
410
|
lines.push(
|
|
436
411
|
'',
|
|
437
412
|
'**For every user message in this session, you MUST call `channel_reply`',
|
|
438
|
-
'(or `channel_send`) at least once before ending your turn**, unless
|
|
439
|
-
'
|
|
440
|
-
'new to add. When you intentionally do not reply, prefer the structured',
|
|
441
|
-
'silent-turn tool over leaking your decision into visible text:',
|
|
413
|
+
'(or `channel_send`) at least once before ending your turn**, unless told',
|
|
414
|
+
'to stay silent or there is nothing genuinely new. If silent, use:',
|
|
442
415
|
'',
|
|
443
|
-
'- **`skip_response({ reason })`** — preferred.
|
|
444
|
-
'
|
|
445
|
-
'
|
|
446
|
-
' sees why. Use this whenever you have a reason worth recording',
|
|
447
|
-
' ("no new info beyond previous reply", "user asked me to stay',
|
|
448
|
-
' silent", "subagent result duplicates what I already sent", etc.).',
|
|
416
|
+
'- **`skip_response({ reason })`** — preferred. Logs a short reason to',
|
|
417
|
+
' host logs (`typeclaw logs -f`) and sends nothing. Use for recorded',
|
|
418
|
+
' silence (duplicate/no new info/user asked for silence, etc.).',
|
|
449
419
|
' The contract is bidirectional: after calling `skip_response`, any',
|
|
450
|
-
' `channel_reply`/`channel_send` in the same turn will be rejected
|
|
451
|
-
' AND calling `skip_response` after a reply has already landed in',
|
|
452
|
-
'
|
|
453
|
-
'
|
|
454
|
-
'
|
|
455
|
-
'
|
|
456
|
-
'
|
|
457
|
-
' call. Use this only when `skip_response` is unavailable or you',
|
|
458
|
-
' have no reason worth recording. Any other visible text without a',
|
|
459
|
-
' channel tool call is blocked.',
|
|
420
|
+
' `channel_reply`/`channel_send` in the same turn will be rejected.',
|
|
421
|
+
' AND calling `skip_response` after a reply has already landed in this turn',
|
|
422
|
+
' will also be rejected. Commit to silence or commit to replying, not both.',
|
|
423
|
+
' Do not include secrets or long reasoning; keep it under one sentence.',
|
|
424
|
+
'- **`NO_REPLY` text sentinel** — fallback. End with exactly `NO_REPLY`',
|
|
425
|
+
' and no channel tool call when `skip_response` is unavailable or not',
|
|
426
|
+
' worth logging. Any other visible text without a channel tool is blocked.',
|
|
460
427
|
'',
|
|
461
|
-
'
|
|
462
|
-
'back into FUTURE turns, use the engagement tool below.',
|
|
428
|
+
'Those silence only the CURRENT turn. To stop being pulled back into FUTURE turns:',
|
|
463
429
|
'',
|
|
464
430
|
'- **`channel_disengage()`** — drop "mid-conversation" stickiness for this',
|
|
465
|
-
' conversation.
|
|
466
|
-
'
|
|
467
|
-
'
|
|
468
|
-
'
|
|
469
|
-
'
|
|
470
|
-
'
|
|
471
|
-
' reply, or DM). It sends no message and does not affect other channels.',
|
|
472
|
-
' ORDER MATTERS: if you want to ack ("ok, backing off") before going quiet,',
|
|
473
|
-
" send that `channel_reply` FIRST, THEN call `channel_disengage` — it's the",
|
|
474
|
-
' natural terminal action for the turn. Pair it with `skip_response` when',
|
|
475
|
-
' you also want to stay silent this turn.',
|
|
431
|
+
' conversation. Call when someone asks you to be quiet / stop replying,',
|
|
432
|
+
' or when you are in a redundant loop. Afterward you re-engage',
|
|
433
|
+
' only when explicitly addressed again (mention, reply, or DM). It sends',
|
|
434
|
+
' no message and affects no other channel. ORDER MATTERS: to ack before',
|
|
435
|
+
' quieting, send that `channel_reply` FIRST, THEN call `channel_disengage`.',
|
|
436
|
+
' Pair with `skip_response` if staying silent this turn too.',
|
|
476
437
|
'',
|
|
477
|
-
' **An explicit quiet command is a direct order to call this tool.**
|
|
478
|
-
'
|
|
479
|
-
' "
|
|
480
|
-
' "
|
|
481
|
-
'
|
|
482
|
-
'
|
|
483
|
-
'
|
|
484
|
-
'
|
|
485
|
-
' pulled back in — exactly what they told you to stop. The acknowledgement',
|
|
486
|
-
' does not disengage you; the tool call does. If you ack, ack FIRST with',
|
|
487
|
-
' `channel_reply`, THEN call `channel_disengage`; if you would rather go',
|
|
488
|
-
' quiet without a word, call `channel_disengage` alone (optionally with',
|
|
489
|
-
' `skip_response`). Match intent, not exact words: any clear request to',
|
|
490
|
-
' stop participating counts, whatever the phrasing or language.',
|
|
438
|
+
' **An explicit quiet command is a direct order to call this tool.**',
|
|
439
|
+
' Examples: "disengage", "be quiet", "stop replying", "stop", "back off",',
|
|
440
|
+
' "stay out of this", "shush", "조용" / "조용히 해" / "그만" / "빠져" /',
|
|
441
|
+
' "тихо" / "tais-toi" / "cállate" / "ruhig" / "黙って" / "安静". For any',
|
|
442
|
+
' clear stop request in any language, you MUST call `channel_disengage`.',
|
|
443
|
+
' An ack alone is not enough/does not disengage; it re-grants stickiness.',
|
|
444
|
+
' If acking, ack FIRST with `channel_reply`, THEN call `channel_disengage`;',
|
|
445
|
+
' otherwise call `channel_disengage` alone (optionally with `skip_response`).',
|
|
491
446
|
'',
|
|
492
447
|
'**Every user-facing sentence goes through `channel_reply`.** Narrating in',
|
|
493
448
|
'plain text — "bumping to 16x now", "let me check that" — does NOT reach the',
|
|
494
|
-
'user; it is invisible. If
|
|
495
|
-
'
|
|
449
|
+
'user; it is invisible. If the user should see it, use `channel_reply`.',
|
|
450
|
+
'This includes acks.',
|
|
496
451
|
'',
|
|
497
452
|
'**One substantive reply per inbound.** If the answer needs more than one',
|
|
498
453
|
...(origin.adapter === 'github'
|
|
499
454
|
? [
|
|
500
455
|
'tool call, keep working and post the answer with a single final',
|
|
501
456
|
'`channel_reply`. Do not post an "On it" ack comment first — the runtime',
|
|
502
|
-
'already added an :eyes: reaction
|
|
503
|
-
'want to acknowledge explicitly. The answer is your reply.',
|
|
457
|
+
'already added an :eyes: reaction; use `channel_react` for explicit ack.',
|
|
504
458
|
]
|
|
505
459
|
: [
|
|
506
460
|
'tool call, send a one-line ack first via `channel_reply({ text: "On it.",',
|
|
507
461
|
'continue: true })`, keep working, then send the answer with a final',
|
|
508
462
|
'`channel_reply`. The ack is not your reply; the answer is. Once the answer',
|
|
509
|
-
'lands, end your turn.
|
|
510
|
-
'
|
|
511
|
-
'work — the fetch, the subagent, the actual answer — is silently dropped.',
|
|
463
|
+
'lands, end your turn. `continue: true` is mandatory or the turn ends at',
|
|
464
|
+
'the ack and drops the fetch/subagent/actual answer.',
|
|
512
465
|
]),
|
|
513
466
|
'',
|
|
514
467
|
'**Backgrounded work does not end the obligation.** If you spawn a',
|
|
515
468
|
'subagent with `run_in_background: true` to answer the current inbound,',
|
|
516
|
-
|
|
517
|
-
'
|
|
518
|
-
'
|
|
519
|
-
'
|
|
520
|
-
'
|
|
521
|
-
'
|
|
522
|
-
'result is empty or identical to something you already replied with',
|
|
523
|
-
'this conversation) — and in that case, `skip_response({ reason: "..." })`',
|
|
524
|
-
'is preferred so the operator can see why the result was dropped.',
|
|
469
|
+
'you promised a reply you have not delivered. Do not skip: the system will',
|
|
470
|
+
'not surface the result. When the subagent-completion `<system-reminder>` arrives,',
|
|
471
|
+
'call `subagent_output` and send the result via `channel_reply`.',
|
|
472
|
+
'`skip_response` (or `NO_REPLY`) is only legal on the post-result turn if',
|
|
473
|
+
'there is nothing user-facing to share; prefer `skip_response` so the',
|
|
474
|
+
'operator can see why it was dropped.',
|
|
525
475
|
'',
|
|
526
|
-
'Do not send a second reply just to rephrase, restate, or "confirm
|
|
527
|
-
'plain language" something you already said.',
|
|
476
|
+
'Do not send a second reply just to rephrase, restate, or "confirm" what you already said.',
|
|
528
477
|
'',
|
|
529
|
-
'To reply
|
|
530
|
-
`
|
|
531
|
-
'
|
|
478
|
+
'To reply here, call `channel_reply({ text })`. Addressing (including the',
|
|
479
|
+
`thread${origin.thread !== null ? '' : ' — none here, this is a channel-root session'}) is filled in; you don't need`,
|
|
480
|
+
'to copy these fields:',
|
|
532
481
|
'',
|
|
533
482
|
'```json',
|
|
534
483
|
'{',
|
|
@@ -539,11 +488,8 @@ function renderChannelOrigin(
|
|
|
539
488
|
'}',
|
|
540
489
|
'```',
|
|
541
490
|
'',
|
|
542
|
-
'To post somewhere else (different chat,
|
|
543
|
-
'
|
|
544
|
-
'`channel_send` and pass the addressing fields explicitly. Only chats',
|
|
545
|
-
"matching the channel's `allow` rules are accepted (the tool returns",
|
|
546
|
-
'`{ ok: false }` otherwise).',
|
|
491
|
+
'To post somewhere else (different chat, leaving the thread, DM, etc.), use',
|
|
492
|
+
'`channel_send` with explicit addressing. `allow` rules still apply (`{ ok: false }`).',
|
|
547
493
|
'',
|
|
548
494
|
...renderResearchReportDeliveryGuidance(platformInfo),
|
|
549
495
|
...renderMentionGuidance(platformInfo, origin.participants ?? [], now, origin.self),
|
|
@@ -578,7 +524,7 @@ function renderMembershipSummary(
|
|
|
578
524
|
if (isExact) {
|
|
579
525
|
return `This channel has ${total} members: ${membership.humans} humans, ${membership.bots} bots.${caveat} The 10 most recent speakers are listed below.`
|
|
580
526
|
}
|
|
581
|
-
return `This channel has approximately ${total} members (about ${membership.humans} humans, ${membership.bots} bots
|
|
527
|
+
return `This channel has approximately ${total} members (about ${membership.humans} humans, ${membership.bots} bots; bot count approximate because the full member list exceeds the 50-member cap). The 10 most recent speakers are listed below.`
|
|
582
528
|
}
|
|
583
529
|
|
|
584
530
|
// The `researcher` subagent always hands back a markdown report file
|
|
@@ -593,21 +539,15 @@ function renderResearchReportDeliveryGuidance(platformInfo: PlatformInfo): strin
|
|
|
593
539
|
if (!platformInfo.supportsAttachments) return []
|
|
594
540
|
return [
|
|
595
541
|
`**Ship reports as a PDF by default.** ${platformInfo.displayName} accepts file`,
|
|
596
|
-
'attachments. When the user asks for a report, document, brief, or "the report"',
|
|
597
|
-
'
|
|
598
|
-
'
|
|
599
|
-
'
|
|
600
|
-
'
|
|
601
|
-
'
|
|
602
|
-
|
|
603
|
-
'
|
|
604
|
-
|
|
605
|
-
'never ship a tofu-rendered PDF; if the output has tofu boxes, ask before',
|
|
606
|
-
'enabling the opt-in `cjkFonts` and restarting.',
|
|
607
|
-
'A downloadable file is what a human wants for a multi-page report; do not paste',
|
|
608
|
-
'the full markdown into chat, and do not attach the raw `.md` when asked for a',
|
|
609
|
-
'report or PDF. Send inline plain text only if the caller explicitly asked for it,',
|
|
610
|
-
'or the content is short enough that a file would be overkill.',
|
|
542
|
+
'attachments. When the user asks for a report, document, brief, or "the report", or when a',
|
|
543
|
+
'`researcher` subagent returns `research-<slug>.md` in `<report>`, render the',
|
|
544
|
+
'markdown with `typeclaw-render-pdf` and deliver via',
|
|
545
|
+
'`channel_send({ ..., attachments: [{ path, filename }] })` plus a 1–2 line',
|
|
546
|
+
'summary. A `researcher` `<summary>` is a teaser, NOT the deliverable. Never',
|
|
547
|
+
'use an ad-hoc library (jsPDF, pdfkit, raw-text dump); it breaks markdown/CJK.',
|
|
548
|
+
"For Korean/Japanese/Chinese, follow the skill's CJK guidance and never ship",
|
|
549
|
+
'tofu boxes. Do not paste the full markdown into chat; do not attach the raw `.md`',
|
|
550
|
+
'unless explicitly asked; inline text is only for short content.',
|
|
611
551
|
'',
|
|
612
552
|
]
|
|
613
553
|
}
|
|
@@ -632,26 +572,24 @@ function renderMentionGuidance(
|
|
|
632
572
|
return [
|
|
633
573
|
`To mention someone in your reply, use ${platformInfo.displayName} syntax \`<@USER_ID>\`.`,
|
|
634
574
|
`For example, to address ${exampleName} in this conversation, write \`<@${exampleId}> hello\` —`,
|
|
635
|
-
`**not** "${exampleName} hello". Plain-text names do not notify
|
|
636
|
-
'and
|
|
575
|
+
`**not** "${exampleName} hello". Plain-text names do not notify on ${platformInfo.displayName},`,
|
|
576
|
+
'and peer bots will not treat the message as addressed to them.',
|
|
637
577
|
...renderSelfMention(platformInfo, self),
|
|
638
578
|
]
|
|
639
579
|
case 'at-username':
|
|
640
580
|
return [
|
|
641
581
|
`To mention someone in your reply, use Telegram syntax \`@username\` in plain text.`,
|
|
642
|
-
|
|
643
|
-
'
|
|
644
|
-
'If
|
|
645
|
-
'and they will see the message via the reply context.',
|
|
582
|
+
'Telegram usernames are a SEPARATE field from `authorId`; `<@id>` tokens',
|
|
583
|
+
'in participants are inbound-only typeclaw markers, so do not echo them back as outbound mentions.',
|
|
584
|
+
'If no `@username` is known, use display name; reply context carries it.',
|
|
646
585
|
...renderSelfMention(platformInfo, self),
|
|
647
586
|
]
|
|
648
587
|
case 'alias':
|
|
649
588
|
return [
|
|
650
589
|
'KakaoTalk has no in-band mention syntax. To address someone, just type their display name as plain text;',
|
|
651
|
-
"the participants block
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
'do not echo them back as outbound mentions; KakaoTalk would render them as literal text.',
|
|
590
|
+
"the participants block shows display names. Users get the bot's attention with configured aliases,",
|
|
591
|
+
'not copied tokens. Any `<@id>` marker is inbound-only typeclaw convention;',
|
|
592
|
+
'do not echo them back as outbound mentions or KakaoTalk renders them literally.',
|
|
655
593
|
]
|
|
656
594
|
}
|
|
657
595
|
}
|
|
@@ -672,8 +610,7 @@ function renderSelfMention(platformInfo: PlatformInfo, self: ChannelSelfIdentity
|
|
|
672
610
|
return [
|
|
673
611
|
'',
|
|
674
612
|
`**You are ${forms} on this ${platformInfo.displayName} workspace.** When a message`,
|
|
675
|
-
|
|
676
|
-
'someone else, and do not skip the turn as "addressed to another user".',
|
|
613
|
+
'contains your id, it is addressed to YOU — do not skip it as "addressed to another user".',
|
|
677
614
|
]
|
|
678
615
|
}
|
|
679
616
|
case 'at-username': {
|
|
@@ -681,8 +618,7 @@ function renderSelfMention(platformInfo: PlatformInfo, self: ChannelSelfIdentity
|
|
|
681
618
|
return [
|
|
682
619
|
'',
|
|
683
620
|
`**You are \`@${self.username}\` on ${platformInfo.displayName}.** A message mentioning`,
|
|
684
|
-
`\`@${self.username}\` is addressed to YOU — treat it as
|
|
685
|
-
'someone else.',
|
|
621
|
+
`\`@${self.username}\` is addressed to YOU — treat it as self-mention.`,
|
|
686
622
|
]
|
|
687
623
|
}
|
|
688
624
|
case 'alias':
|
|
@@ -727,11 +663,8 @@ function renderParticipants(
|
|
|
727
663
|
}
|
|
728
664
|
lines.push(
|
|
729
665
|
'',
|
|
730
|
-
'This list is **bounded
|
|
731
|
-
'
|
|
732
|
-
'days. Older or less recent authors are not shown even if they exist.',
|
|
733
|
-
'This is **not** the full guild member list, and **not** an audit log',
|
|
734
|
-
'of everyone who ever spoke here.',
|
|
666
|
+
'This list is **bounded**: only the 10 most recent authors from the last',
|
|
667
|
+
'7 days. It is **not** the full guild member list or an audit log.',
|
|
735
668
|
'',
|
|
736
669
|
...renderParticipantsTrailing(platformInfo),
|
|
737
670
|
)
|
|
@@ -774,26 +707,21 @@ function renderParticipantsTrailing(platformInfo: PlatformInfo): string[] {
|
|
|
774
707
|
switch (platformInfo.mentionMode) {
|
|
775
708
|
case 'angle-id':
|
|
776
709
|
return [
|
|
777
|
-
"If a
|
|
778
|
-
'
|
|
779
|
-
'
|
|
780
|
-
'not an exhaustive directory.',
|
|
710
|
+
"If a current sender isn't listed, you can still address them —",
|
|
711
|
+
'`<@authorId>` works for any author you have seen once. The list is',
|
|
712
|
+
'recent-context convenience, not an exhaustive directory.',
|
|
781
713
|
]
|
|
782
714
|
case 'at-username':
|
|
783
715
|
return [
|
|
784
|
-
"If a
|
|
785
|
-
'
|
|
786
|
-
'
|
|
787
|
-
'user has one. The list is a convenience for "who\'s been around',
|
|
788
|
-
'lately," not an exhaustive directory.',
|
|
716
|
+
"If a current sender isn't listed, address by `@username` when known.",
|
|
717
|
+
'Telegram usernames are a SEPARATE field from numeric `authorId`, and',
|
|
718
|
+
'not every user has one. The list is recent context, not a directory.',
|
|
789
719
|
]
|
|
790
720
|
case 'alias':
|
|
791
721
|
return [
|
|
792
|
-
"If a
|
|
793
|
-
'
|
|
794
|
-
'
|
|
795
|
-
'your reference only and must not be echoed back. The list is a',
|
|
796
|
-
'convenience for "who\'s been around lately," not an exhaustive directory.',
|
|
722
|
+
"If a current sender isn't listed, address by display name as plain text.",
|
|
723
|
+
'KakaoTalk has no in-band mention syntax; `authorId` is reference only',
|
|
724
|
+
'and must not be echoed back. The list is recent context, not a directory.',
|
|
797
725
|
]
|
|
798
726
|
}
|
|
799
727
|
}
|