typeclaw 0.37.2 → 0.37.4
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 +71 -47
- package/package.json +1 -1
- package/src/agent/compaction.ts +24 -15
- package/src/agent/session-origin.ts +101 -173
- package/src/agent/system-prompt.ts +46 -48
- package/src/bundled-plugins/memory/index.ts +24 -27
- package/src/bundled-plugins/memory/load-memory.ts +78 -35
- package/src/bundled-plugins/memory/turn-dedup.ts +32 -29
- 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/mention-hints.ts +58 -0
- package/src/channels/adapters/slack-bot.ts +8 -2
- package/src/channels/continuation-willingness.ts +265 -53
- package/src/channels/router.ts +105 -3
- package/src/cli/init.ts +41 -7
- package/src/cli/qr.ts +4 -3
- package/src/cli/ui.ts +8 -4
- package/src/doctor/checks.ts +145 -2
- package/src/hostd/tailscale.ts +12 -1
- package/src/init/index.ts +35 -8
- package/src/init/run-bun-install.ts +71 -37
- package/src/inspect/transcript-view.ts +15 -2
- package/src/portbroker/hostd-client.ts +32 -6
- 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
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
registerCommands,
|
|
49
49
|
type DiscordCommandDeclaration,
|
|
50
50
|
} from './discord-bot-slash-commands'
|
|
51
|
+
import { addDiscordMentionHints, type DiscordMentionUser } from './mention-hints'
|
|
51
52
|
|
|
52
53
|
// One declared slash command per logical agent gesture. /stop maps to the
|
|
53
54
|
// existing channel-command of the same name in the router. Adding new
|
|
@@ -507,6 +508,7 @@ type DiscordRawHistoryMessage = {
|
|
|
507
508
|
author: { id: string; username?: string; global_name?: string | null; bot?: boolean }
|
|
508
509
|
content: string
|
|
509
510
|
timestamp: string
|
|
511
|
+
mentions?: DiscordMentionUser[]
|
|
510
512
|
message_reference?: { message_id?: string; channel_id?: string }
|
|
511
513
|
attachments?: DiscordFile[]
|
|
512
514
|
embeds?: DiscordGatewayEmbed[]
|
|
@@ -597,7 +599,7 @@ function mapDiscordMessage(msg: DiscordRawHistoryMessage, botUserId: string | nu
|
|
|
597
599
|
// never resolve them. Mirror the classifier's splitInbound: bake placeholders
|
|
598
600
|
// into text and carry the structured attachments so the router can resolve ids.
|
|
599
601
|
const attachments = describeDiscordMedia(source)
|
|
600
|
-
const text = bodyOf(source)
|
|
602
|
+
const text = addDiscordMentionHints(bodyOf(source), mentionUserMap(source.mentions), { botUserId })
|
|
601
603
|
return {
|
|
602
604
|
externalMessageId: msg.id,
|
|
603
605
|
authorId: source.author.id,
|
|
@@ -617,6 +619,10 @@ function bodyOf(msg: DiscordRawHistoryMessage): string {
|
|
|
617
619
|
return msg.content === '' ? placeholders : `${msg.content}\n${placeholders}`
|
|
618
620
|
}
|
|
619
621
|
|
|
622
|
+
function mentionUserMap(mentions: readonly DiscordMentionUser[] | undefined): Map<string, DiscordMentionUser> {
|
|
623
|
+
return new Map((mentions ?? []).map((user) => [user.id, user]))
|
|
624
|
+
}
|
|
625
|
+
|
|
620
626
|
function clampLimit(requested: number, max: number): number {
|
|
621
627
|
if (!Number.isFinite(requested) || requested <= 0) return max
|
|
622
628
|
return Math.min(Math.floor(requested), max)
|
|
@@ -932,9 +938,10 @@ export function createDiscordBotAdapter(options: DiscordBotAdapterOptions): Disc
|
|
|
932
938
|
return
|
|
933
939
|
}
|
|
934
940
|
|
|
941
|
+
const hintedText = addDiscordMentionHints(verdict.payload.text, mentionUserMap(event.mentions), { botUserId })
|
|
935
942
|
const replyMessageId = event.message_reference?.message_id
|
|
936
943
|
const referenceResult = await enrichDiscordMessageReferences({
|
|
937
|
-
text:
|
|
944
|
+
text: hintedText,
|
|
938
945
|
...(replyMessageId !== undefined
|
|
939
946
|
? { reply: { channelId: event.message_reference?.channel_id ?? event.channel_id, messageId: replyMessageId } }
|
|
940
947
|
: {}),
|
|
@@ -950,8 +957,8 @@ export function createDiscordBotAdapter(options: DiscordBotAdapterOptions): Disc
|
|
|
950
957
|
})
|
|
951
958
|
const payload =
|
|
952
959
|
referenceResult.referenceContext === undefined
|
|
953
|
-
? verdict.payload
|
|
954
|
-
: { ...verdict.payload, referenceContext: referenceResult.referenceContext }
|
|
960
|
+
? { ...verdict.payload, text: hintedText }
|
|
961
|
+
: { ...verdict.payload, text: hintedText, referenceContext: referenceResult.referenceContext }
|
|
955
962
|
|
|
956
963
|
const routedTag = await formatChannelTag(payload.workspace, payload.chat)
|
|
957
964
|
logger.info(
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type DiscordMentionUser = { id: string; username?: string; global_name?: string | null }
|
|
2
|
+
|
|
3
|
+
export type MentionHintOptions = { botUserId?: string | null }
|
|
4
|
+
|
|
5
|
+
// Slack encodes user mentions as `<@U…>`/`<@W…>`, optionally with a native
|
|
6
|
+
// `|label` fallback suffix. We capture the id and the whole token so the bare
|
|
7
|
+
// `<@id>` can be reconstructed (dropping any legacy label) and a resolved hint
|
|
8
|
+
// appended after it.
|
|
9
|
+
const SLACK_MENTION_PATTERN = /<@([UW][A-Z0-9]+)(?:\|[^>]*)?>/g
|
|
10
|
+
|
|
11
|
+
// Discord uses `<@id>` and the nickname form `<@!id>`; the `!` is optional and
|
|
12
|
+
// irrelevant to the target user, so it is captured but discarded on rewrite.
|
|
13
|
+
const DISCORD_MENTION_PATTERN = /<@!?(\d+)>/g
|
|
14
|
+
|
|
15
|
+
export async function addSlackMentionHints(
|
|
16
|
+
text: string,
|
|
17
|
+
resolveUserName: (id: string) => Promise<string>,
|
|
18
|
+
options: MentionHintOptions = {},
|
|
19
|
+
): Promise<string> {
|
|
20
|
+
const ids = new Set<string>()
|
|
21
|
+
for (const match of text.matchAll(SLACK_MENTION_PATTERN)) ids.add(match[1]!)
|
|
22
|
+
if (ids.size === 0) return text
|
|
23
|
+
|
|
24
|
+
const hints = new Map<string, string>()
|
|
25
|
+
await Promise.all(
|
|
26
|
+
Array.from(ids).map(async (id) => {
|
|
27
|
+
const hint = resolveHint(id, await resolveUserName(id), options.botUserId)
|
|
28
|
+
if (hint !== null) hints.set(id, hint)
|
|
29
|
+
}),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return text.replace(SLACK_MENTION_PATTERN, (_token, id: string) => renderToken(id, hints.get(id)))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function addDiscordMentionHints(
|
|
36
|
+
text: string,
|
|
37
|
+
usersById: Map<string, DiscordMentionUser>,
|
|
38
|
+
options: MentionHintOptions = {},
|
|
39
|
+
): string {
|
|
40
|
+
return text.replace(DISCORD_MENTION_PATTERN, (token, id: string) => {
|
|
41
|
+
const user = usersById.get(id)
|
|
42
|
+
const name = user === undefined ? id : (user.global_name ?? user.username ?? id)
|
|
43
|
+
const hint = resolveHint(id, name, options.botUserId)
|
|
44
|
+
return hint === null ? token : `${token} (${hint})`
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolveHint(id: string, resolvedName: string, botUserId: string | null | undefined): string | null {
|
|
49
|
+
if (id === botUserId) return 'you'
|
|
50
|
+
// The resolver echoes the id back when it cannot find a name; a bare id is
|
|
51
|
+
// not a useful hint, so leave the token unannotated in that case.
|
|
52
|
+
if (resolvedName === id || resolvedName === '') return null
|
|
53
|
+
return resolvedName
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function renderToken(id: string, hint: string | undefined): string {
|
|
57
|
+
return hint === undefined ? `<@${id}>` : `<@${id}> (${hint})`
|
|
58
|
+
}
|
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
} from '@/channels/types'
|
|
33
33
|
import { chunkMarkdown } from '@/markdown'
|
|
34
34
|
|
|
35
|
+
import { addSlackMentionHints } from './mention-hints'
|
|
35
36
|
import { createSlackAuthorResolver, type SlackAuthorResolver } from './slack-bot-author-resolver'
|
|
36
37
|
import { createSlackChannelResolver } from './slack-bot-channel-resolver'
|
|
37
38
|
import {
|
|
@@ -703,11 +704,14 @@ export function createSlackHistoryCallback(deps: {
|
|
|
703
704
|
// users.info entry. The resolver caches/coalesces, so repeated authors
|
|
704
705
|
// cost one lookup each.
|
|
705
706
|
if (authorResolver !== undefined) {
|
|
707
|
+
const resolver = authorResolver
|
|
708
|
+
const botId = botUserIdRef()
|
|
706
709
|
await Promise.all(
|
|
707
710
|
mapped.map(async (message, index) => {
|
|
711
|
+
message.text = await addSlackMentionHints(message.text, resolver.resolve, { botUserId: botId })
|
|
708
712
|
const userId = rawMessages[index]?.user
|
|
709
713
|
if (userId === undefined || userId === '') return
|
|
710
|
-
message.authorName = await
|
|
714
|
+
message.authorName = await resolver.resolve(userId)
|
|
711
715
|
}),
|
|
712
716
|
)
|
|
713
717
|
}
|
|
@@ -1133,9 +1137,10 @@ export function createSlackBotAdapter(options: SlackBotAdapterOptions): SlackBot
|
|
|
1133
1137
|
}
|
|
1134
1138
|
|
|
1135
1139
|
dedupe.mark(event)
|
|
1140
|
+
const hintedText = await addSlackMentionHints(verdict.payload.text, authorResolver.resolve, { botUserId })
|
|
1136
1141
|
const slackAttachments = Array.isArray(event.attachments) ? event.attachments : undefined
|
|
1137
1142
|
const referenceResult = await enrichSlackReferenceContext({
|
|
1138
|
-
text:
|
|
1143
|
+
text: hintedText,
|
|
1139
1144
|
channelId: event.channel,
|
|
1140
1145
|
messageTs: event.ts,
|
|
1141
1146
|
...(slackAttachments !== undefined ? { attachments: slackAttachments } : {}),
|
|
@@ -1143,6 +1148,7 @@ export function createSlackBotAdapter(options: SlackBotAdapterOptions): SlackBot
|
|
|
1143
1148
|
})
|
|
1144
1149
|
const enriched = {
|
|
1145
1150
|
...verdict.payload,
|
|
1151
|
+
text: hintedText,
|
|
1146
1152
|
authorName: resolvedUserName,
|
|
1147
1153
|
...(referenceResult.referenceContext !== undefined
|
|
1148
1154
|
? { referenceContext: referenceResult.referenceContext }
|
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
// descriptive ("I checked and it's fine") or other-directed ("you can continue")
|
|
14
14
|
// usage. This is a HINT, not a control-flow authority: the abort still fires
|
|
15
15
|
// regardless; only the optional nudge is gated on it.
|
|
16
|
+
//
|
|
17
|
+
// Detection is two-pass: a phrase-substring pass (ALL_PHRASES) for analytic
|
|
18
|
+
// languages where future intent is a separate word ("I'll", "voy a", "我会"),
|
|
19
|
+
// plus a morpheme pass (MORPHEME_PATTERNS + the Japanese check) for languages
|
|
20
|
+
// where it is a verb inflection/affix. The morpheme pass matches the marker
|
|
21
|
+
// itself, so it generalizes across EVERY action verb (update/configure/fix/…)
|
|
22
|
+
// instead of enumerating each — what the per-verb KO/TR/HI/JA lists used to do
|
|
23
|
+
// by hand.
|
|
16
24
|
|
|
17
25
|
// Strip markdown emphasis/code fences before matching so an inline `gh` span
|
|
18
26
|
// inside "바로 `gh`로 확인할게요" does not split the phrase.
|
|
@@ -48,51 +56,94 @@ const EN_PHRASES: readonly string[] = [
|
|
|
48
56
|
'looking into it now',
|
|
49
57
|
'working on it now',
|
|
50
58
|
'on it now',
|
|
59
|
+
"i'm on it",
|
|
51
60
|
'give me a moment',
|
|
52
61
|
'give me a sec',
|
|
62
|
+
// Parity additions for common first-person-future acks: "investigate / look up
|
|
63
|
+
// / pull up" are work-verb siblings of the "look into / dig in" entries above,
|
|
64
|
+
// and "lemme" is the contracted "let me" that chat models routinely emit.
|
|
65
|
+
"i'll investigate",
|
|
66
|
+
"i'll look it up",
|
|
67
|
+
"i'll pull that up",
|
|
68
|
+
"i'll pull it up",
|
|
69
|
+
'let me pull',
|
|
70
|
+
'lemme check',
|
|
71
|
+
'lemme look',
|
|
72
|
+
'lemme take a look',
|
|
73
|
+
// Action/config verb family. The retrieval verbs above ("check/look/look up")
|
|
74
|
+
// miss the much larger class of "I'll DO X" promises — update/configure/set up/
|
|
75
|
+
// schedule/fix/apply/create — which is exactly the class that silently truncates
|
|
76
|
+
// when the model forgets `continue: true` (the cron-update production miss).
|
|
77
|
+
"i'll update",
|
|
78
|
+
"i'll set up",
|
|
79
|
+
"i'll set it up",
|
|
80
|
+
"i'll configure",
|
|
81
|
+
"i'll schedule",
|
|
82
|
+
"i'll fix",
|
|
83
|
+
"i'll apply",
|
|
84
|
+
"i'll add",
|
|
85
|
+
"i'll create",
|
|
86
|
+
"i'll handle",
|
|
87
|
+
'let me update',
|
|
88
|
+
'let me fix',
|
|
89
|
+
'let me set up',
|
|
90
|
+
'let me configure',
|
|
91
|
+
'let me add',
|
|
92
|
+
'let me create',
|
|
93
|
+
'let me handle',
|
|
53
94
|
]
|
|
54
95
|
|
|
55
|
-
// Korean:
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
96
|
+
// Korean: the -겠습니다/-겠어요 and -ㄹ게요 verb endings are first-person
|
|
97
|
+
// volitional — they cannot address the listener, so they are safe self-direction
|
|
98
|
+
// anchors. The -겠습니다/-겠어요 form is matched by MORPHEME_PATTERNS below (it
|
|
99
|
+
// generalizes across all action verbs), so only the -게요/-게여 forms and stall
|
|
100
|
+
// idioms are enumerated here. Bare adverb+noun fragments ("바로 확인", "계속 확인",
|
|
101
|
+
// "곧 알려") are deliberately NOT listed: without the volitional ending they match
|
|
102
|
+
// other-directed requests ("바로 확인 부탁드려요" = "please check") and descriptive
|
|
103
|
+
// progressives ("계속 확인 중입니다" = "I'm still checking") — the exact false
|
|
104
|
+
// positives the design forbids. Their volitional forms are caught by the morpheme
|
|
105
|
+
// regex regardless.
|
|
60
106
|
const KO_PHRASES: readonly string[] = [
|
|
61
107
|
'확인해볼게요',
|
|
62
108
|
'확인해 볼게요',
|
|
63
109
|
'확인할게요',
|
|
64
|
-
'
|
|
65
|
-
'확인해보겠습니다',
|
|
66
|
-
'확인해 보겠습니다',
|
|
67
|
-
'다시 확인하겠습니다',
|
|
68
|
-
'다시 확인해보겠습니다',
|
|
69
|
-
'이어서 확인',
|
|
70
|
-
'계속 확인',
|
|
110
|
+
'확인할게여',
|
|
71
111
|
'계속 진행할게요',
|
|
72
|
-
'계속 진행하겠습니다',
|
|
73
|
-
'계속하겠습니다',
|
|
74
112
|
'계속할게요',
|
|
75
|
-
'바로 확인',
|
|
76
113
|
'바로 볼게요',
|
|
77
|
-
'바로 진행',
|
|
78
114
|
'살펴볼게요',
|
|
79
|
-
'
|
|
80
|
-
'
|
|
115
|
+
'볼게요',
|
|
116
|
+
'볼게여',
|
|
117
|
+
'검토할게요',
|
|
118
|
+
'검토해볼게요',
|
|
119
|
+
'조회해볼게요',
|
|
120
|
+
'찾아볼게요',
|
|
121
|
+
'알아볼게요',
|
|
122
|
+
'처리할게요',
|
|
123
|
+
'알려드릴게요',
|
|
124
|
+
// Action/config verb -게요 forms (the -겠습니다 siblings are covered by the
|
|
125
|
+
// morpheme regex; these are the casual-polite variants chat models also emit).
|
|
126
|
+
'업데이트할게요',
|
|
127
|
+
'수정할게요',
|
|
128
|
+
'설정할게요',
|
|
129
|
+
'반영할게요',
|
|
130
|
+
'적용할게요',
|
|
131
|
+
'추가할게요',
|
|
132
|
+
'생성할게요',
|
|
81
133
|
'잠시만요',
|
|
82
134
|
'잠깐만요',
|
|
83
|
-
'곧 알려',
|
|
84
135
|
]
|
|
85
136
|
|
|
86
137
|
// The remaining languages mirror the precision-first selection above: every
|
|
87
138
|
// entry pairs a FIRST-PERSON future/volitional anchor with a work verb
|
|
88
|
-
// (check/look/continue/proceed/verify) or is an
|
|
89
|
-
// now"). The same false-negative bias holds — bare
|
|
90
|
-
// ("ok", "sí", "好"), second-person imperatives ("you
|
|
91
|
-
// descriptive past forms ("I checked") are deliberately excluded
|
|
92
|
-
// substring match on those would mis-fire. Latin/Cyrillic/Arabic/Indic
|
|
93
|
-
// are inflected first-person-future forms (or multi-word) so they cannot
|
|
94
|
-
// collide with a bare common word; CJK entries are full 4+ character
|
|
95
|
-
//
|
|
139
|
+
// (check/look/continue/proceed/verify or update/configure/fix/create) or is an
|
|
140
|
+
// immediate-work idiom ("on it now"). The same false-negative bias holds — bare
|
|
141
|
+
// verbs, bare acknowledgments ("ok", "sí", "好"), second-person imperatives ("you
|
|
142
|
+
// continue"), and descriptive past forms ("I checked") are deliberately excluded
|
|
143
|
+
// because a substring match on those would mis-fire. Latin/Cyrillic/Arabic/Indic
|
|
144
|
+
// entries are inflected first-person-future forms (or multi-word) so they cannot
|
|
145
|
+
// collide with a bare common word; CJK entries are full 4+ character intent
|
|
146
|
+
// phrases, never a lone noun.
|
|
96
147
|
|
|
97
148
|
// Spanish: "voy a" / "déjame" + work verb; "enseguida" (right away) idioms.
|
|
98
149
|
const ES_PHRASES: readonly string[] = [
|
|
@@ -106,6 +157,12 @@ const ES_PHRASES: readonly string[] = [
|
|
|
106
157
|
'déjame comprobar',
|
|
107
158
|
'déjame verificar',
|
|
108
159
|
'déjame mirar',
|
|
160
|
+
'voy a echar un vistazo',
|
|
161
|
+
'déjame echar un vistazo',
|
|
162
|
+
'ahora lo reviso',
|
|
163
|
+
'ahora reviso',
|
|
164
|
+
'ahora lo verifico',
|
|
165
|
+
'ahora mismo lo reviso',
|
|
109
166
|
'lo reviso enseguida',
|
|
110
167
|
'lo verifico enseguida',
|
|
111
168
|
'enseguida lo reviso',
|
|
@@ -113,6 +170,17 @@ const ES_PHRASES: readonly string[] = [
|
|
|
113
170
|
'un momento',
|
|
114
171
|
'dame un momento',
|
|
115
172
|
'dame un segundo',
|
|
173
|
+
// Action/config verb family.
|
|
174
|
+
'voy a actualizar',
|
|
175
|
+
'voy a configurar',
|
|
176
|
+
'voy a corregir',
|
|
177
|
+
'voy a arreglar',
|
|
178
|
+
'voy a crear',
|
|
179
|
+
'voy a añadir',
|
|
180
|
+
'voy a programar',
|
|
181
|
+
'voy a aplicar',
|
|
182
|
+
'déjame actualizar',
|
|
183
|
+
'déjame corregir',
|
|
116
184
|
]
|
|
117
185
|
|
|
118
186
|
// French: "je vais" + work verb; "laisse-moi" idioms.
|
|
@@ -123,13 +191,28 @@ const FR_PHRASES: readonly string[] = [
|
|
|
123
191
|
'je vais poursuivre',
|
|
124
192
|
'je vais voir',
|
|
125
193
|
'je vais contrôler',
|
|
194
|
+
'je vais creuser',
|
|
195
|
+
'je vais jeter un œil',
|
|
126
196
|
'laisse-moi vérifier',
|
|
127
197
|
'laisse-moi regarder',
|
|
198
|
+
'laisse-moi jeter un œil',
|
|
128
199
|
'je vérifie tout de suite',
|
|
129
200
|
'je regarde tout de suite',
|
|
201
|
+
'je regarde ça tout de suite',
|
|
202
|
+
'je regarde ça',
|
|
130
203
|
'un instant',
|
|
131
204
|
'donne-moi un instant',
|
|
132
205
|
'donne-moi une seconde',
|
|
206
|
+
// Action/config verb family.
|
|
207
|
+
'je vais mettre à jour',
|
|
208
|
+
'je vais configurer',
|
|
209
|
+
'je vais corriger',
|
|
210
|
+
'je vais créer',
|
|
211
|
+
'je vais ajouter',
|
|
212
|
+
'je vais programmer',
|
|
213
|
+
'je vais appliquer',
|
|
214
|
+
'laisse-moi corriger',
|
|
215
|
+
'laisse-moi mettre à jour',
|
|
133
216
|
]
|
|
134
217
|
|
|
135
218
|
// Italian: "vado a" / "fammi" + work verb; "controllo subito" idioms.
|
|
@@ -137,15 +220,28 @@ const IT_PHRASES: readonly string[] = [
|
|
|
137
220
|
'vado a controllare',
|
|
138
221
|
'vado a verificare',
|
|
139
222
|
'vado a guardare',
|
|
223
|
+
"vado a dare un'occhiata",
|
|
140
224
|
'fammi controllare',
|
|
141
225
|
'fammi verificare',
|
|
142
226
|
'fammi guardare',
|
|
227
|
+
"fammi dare un'occhiata",
|
|
228
|
+
"do un'occhiata",
|
|
143
229
|
'controllo subito',
|
|
144
230
|
'verifico subito',
|
|
145
231
|
'continuo subito',
|
|
232
|
+
'guardo subito',
|
|
146
233
|
'un momento',
|
|
147
234
|
'dammi un momento',
|
|
148
235
|
'dammi un secondo',
|
|
236
|
+
// Action/config verb family.
|
|
237
|
+
'vado ad aggiornare',
|
|
238
|
+
'vado a configurare',
|
|
239
|
+
'vado a correggere',
|
|
240
|
+
'vado a creare',
|
|
241
|
+
'vado ad aggiungere',
|
|
242
|
+
'vado ad applicare',
|
|
243
|
+
'fammi aggiornare',
|
|
244
|
+
'fammi correggere',
|
|
149
245
|
]
|
|
150
246
|
|
|
151
247
|
// Portuguese: "vou" + work verb; "deixa eu" idioms.
|
|
@@ -156,14 +252,26 @@ const PT_PHRASES: readonly string[] = [
|
|
|
156
252
|
'vou olhar',
|
|
157
253
|
'vou continuar',
|
|
158
254
|
'vou prosseguir',
|
|
255
|
+
'vou dar uma olhada',
|
|
159
256
|
'deixa eu verificar',
|
|
160
257
|
'deixa eu conferir',
|
|
161
258
|
'deixa eu olhar',
|
|
259
|
+
'deixa eu dar uma olhada',
|
|
162
260
|
'verifico já',
|
|
163
261
|
'já verifico',
|
|
164
262
|
'um momento',
|
|
165
263
|
'me dê um momento',
|
|
166
264
|
'me dá um segundo',
|
|
265
|
+
// Action/config verb family.
|
|
266
|
+
'vou atualizar',
|
|
267
|
+
'vou configurar',
|
|
268
|
+
'vou corrigir',
|
|
269
|
+
'vou criar',
|
|
270
|
+
'vou adicionar',
|
|
271
|
+
'vou agendar',
|
|
272
|
+
'vou aplicar',
|
|
273
|
+
'deixa eu atualizar',
|
|
274
|
+
'deixa eu corrigir',
|
|
167
275
|
]
|
|
168
276
|
|
|
169
277
|
// German: "ich werde" / "lass mich" + work verb; "ich schaue gleich" idioms.
|
|
@@ -175,18 +283,35 @@ const DE_PHRASES: readonly string[] = [
|
|
|
175
283
|
'ich werde fortfahren',
|
|
176
284
|
'lass mich prüfen',
|
|
177
285
|
'lass mich nachsehen',
|
|
286
|
+
'lass mich schauen',
|
|
178
287
|
'ich schaue gleich',
|
|
288
|
+
'ich schaue mir das an',
|
|
289
|
+
'ich schaue mir das mal an',
|
|
179
290
|
'ich prüfe gleich',
|
|
291
|
+
'ich prüfe das gleich',
|
|
292
|
+
'ich sehe gleich nach',
|
|
180
293
|
'gleich prüfen',
|
|
181
294
|
'gleich überprüfen',
|
|
182
295
|
'gleich nachsehen',
|
|
183
296
|
'einen moment',
|
|
184
297
|
'einen augenblick',
|
|
185
298
|
'gib mir eine sekunde',
|
|
299
|
+
// Action/config verb family.
|
|
300
|
+
'ich werde aktualisieren',
|
|
301
|
+
'ich werde konfigurieren',
|
|
302
|
+
'ich werde korrigieren',
|
|
303
|
+
'ich werde einrichten',
|
|
304
|
+
'ich werde erstellen',
|
|
305
|
+
'ich werde hinzufügen',
|
|
306
|
+
'ich werde anwenden',
|
|
307
|
+
'lass mich aktualisieren',
|
|
308
|
+
'lass mich korrigieren',
|
|
186
309
|
]
|
|
187
310
|
|
|
188
311
|
// Russian: first-person-future verbs (проверю/посмотрю/продолжу) — the -ю/-у
|
|
189
|
-
// inflection is unambiguously "I will", so it is a safe self-anchor.
|
|
312
|
+
// inflection is unambiguously "I will", so it is a safe self-anchor. (Note: the
|
|
313
|
+
// bare -ю ending is shared with present-imperfective "я делаю" = "I do", so this
|
|
314
|
+
// stays an enumerated list rather than a morpheme regex.)
|
|
190
315
|
const RU_PHRASES: readonly string[] = [
|
|
191
316
|
'сейчас проверю',
|
|
192
317
|
'я проверю',
|
|
@@ -194,9 +319,20 @@ const RU_PHRASES: readonly string[] = [
|
|
|
194
319
|
'я продолжу',
|
|
195
320
|
'продолжу проверку',
|
|
196
321
|
'сейчас посмотрю',
|
|
322
|
+
'дай мне проверить',
|
|
323
|
+
'дайте мне проверить',
|
|
197
324
|
'дайте мне минуту',
|
|
198
325
|
'одну секунду',
|
|
199
326
|
'минутку',
|
|
327
|
+
// Action/config verb family (perfective first-person futures).
|
|
328
|
+
'я обновлю',
|
|
329
|
+
'я настрою',
|
|
330
|
+
'я исправлю',
|
|
331
|
+
'я создам',
|
|
332
|
+
'я добавлю',
|
|
333
|
+
'я применю',
|
|
334
|
+
'сейчас обновлю',
|
|
335
|
+
'сейчас исправлю',
|
|
200
336
|
]
|
|
201
337
|
|
|
202
338
|
// Chinese: 我会/我来/我再 + work verb. Full multi-character intent phrases only;
|
|
@@ -214,28 +350,45 @@ const ZH_PHRASES: readonly string[] = [
|
|
|
214
350
|
'我马上确认',
|
|
215
351
|
'我马上检查',
|
|
216
352
|
'我马上看',
|
|
353
|
+
'让我看看',
|
|
354
|
+
'让我查一下',
|
|
355
|
+
'让我确认一下',
|
|
356
|
+
'让我检查一下',
|
|
217
357
|
'稍等一下',
|
|
218
358
|
'我看一下',
|
|
359
|
+
// Action/config verb family.
|
|
360
|
+
'我来更新',
|
|
361
|
+
'我会更新',
|
|
362
|
+
'我来配置',
|
|
363
|
+
'我来设置',
|
|
364
|
+
'我会设置',
|
|
365
|
+
'我来修改',
|
|
366
|
+
'我会修改',
|
|
367
|
+
'我来修复',
|
|
368
|
+
'我来创建',
|
|
369
|
+
'我来添加',
|
|
370
|
+
'我马上更新',
|
|
371
|
+
'让我更新',
|
|
372
|
+
'让我改一下',
|
|
219
373
|
]
|
|
220
374
|
|
|
221
|
-
// Japanese:
|
|
222
|
-
//
|
|
375
|
+
// Japanese: handled by the JA_VOLITIONAL morpheme check below (-します/-いたします/
|
|
376
|
+
// -してみます generalizes across all する action verbs). Only the regular-verb
|
|
377
|
+
// ます forms (調べます/見てみます/続けます — bare ます is too broad to regex) and
|
|
378
|
+
// stall idioms are enumerated here.
|
|
223
379
|
const JA_PHRASES: readonly string[] = [
|
|
224
|
-
'確認します',
|
|
225
|
-
'確認してみます',
|
|
226
|
-
'確認いたします',
|
|
227
380
|
'調べてみます',
|
|
228
381
|
'調べます',
|
|
229
382
|
'見てみます',
|
|
230
383
|
'続けます',
|
|
231
|
-
'引き続き確認します',
|
|
232
|
-
'すぐ確認します',
|
|
233
384
|
'少々お待ちください',
|
|
234
385
|
'ちょっと待ってください',
|
|
235
386
|
]
|
|
236
387
|
|
|
237
388
|
// Arabic: future particle سـ prefixed first-person verb (سأتحقق = "I will
|
|
238
|
-
// verify"). The سأ prefix is
|
|
389
|
+
// verify"). The سأ prefix is first-person-future, but as a bare substring it
|
|
390
|
+
// collides with the root س-أ-ل ("ask": سألت "I asked", المسألة "the matter"), so
|
|
391
|
+
// this stays an enumerated list of full verbs rather than a سأ-prefix regex.
|
|
239
392
|
const AR_PHRASES: readonly string[] = [
|
|
240
393
|
'سأتحقق',
|
|
241
394
|
'سأتأكد',
|
|
@@ -246,27 +399,31 @@ const AR_PHRASES: readonly string[] = [
|
|
|
246
399
|
'دعني أتحقق',
|
|
247
400
|
'دعني أراجع',
|
|
248
401
|
'لحظة من فضلك',
|
|
402
|
+
// Action/config verb family.
|
|
403
|
+
'سأحدث',
|
|
404
|
+
'سأحدّث',
|
|
405
|
+
'سأعدل',
|
|
406
|
+
'سأعدّل',
|
|
407
|
+
'سأضبط',
|
|
408
|
+
'سأصلح',
|
|
409
|
+
'سأنشئ',
|
|
410
|
+
'سأضيف',
|
|
249
411
|
]
|
|
250
412
|
|
|
251
|
-
// Hindi: first-person-future
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
'जांच करूंगा',
|
|
256
|
-
'देख लूँगा',
|
|
257
|
-
'देख लूंगा',
|
|
258
|
-
'जारी रखूँगा',
|
|
259
|
-
'जारी रखूंगा',
|
|
260
|
-
'एक मिनट रुकिए',
|
|
261
|
-
]
|
|
413
|
+
// Hindi: first-person-future is the -ūṅgā/-ūṅgī suffix, matched by
|
|
414
|
+
// MORPHEME_PATTERNS below (it covers all X-करना compounds). Only the stall idiom
|
|
415
|
+
// is enumerated here.
|
|
416
|
+
const HI_PHRASES: readonly string[] = ['एक मिनट रुकिए']
|
|
262
417
|
|
|
263
|
-
// Turkish: first-person-future "-eceğim/-acağım"
|
|
418
|
+
// Turkish: first-person-future "-eceğim/-acağım" is matched by MORPHEME_PATTERNS
|
|
419
|
+
// below. The present-progressive ("ediyorum" = "I'm checking now"), optative
|
|
420
|
+
// ("bir bakayım" = "let me look"), and stall idioms stay enumerated — the
|
|
421
|
+
// progressive -ıyorum ending is too polysemous to regex ("biliyorum" = "I know").
|
|
264
422
|
const TR_PHRASES: readonly string[] = [
|
|
265
|
-
'kontrol edeceğim',
|
|
266
423
|
'kontrol ediyorum',
|
|
267
|
-
'
|
|
268
|
-
'
|
|
269
|
-
'
|
|
424
|
+
'bir bakayım',
|
|
425
|
+
'bir kontrol edeyim',
|
|
426
|
+
'kontrol edeyim',
|
|
270
427
|
'hemen kontrol ediyorum',
|
|
271
428
|
'hemen bakıyorum',
|
|
272
429
|
'bir saniye',
|
|
@@ -284,6 +441,14 @@ const VI_PHRASES: readonly string[] = [
|
|
|
284
441
|
'tôi xem ngay',
|
|
285
442
|
'chờ một chút',
|
|
286
443
|
'đợi một chút',
|
|
444
|
+
// Action/config verb family.
|
|
445
|
+
'tôi sẽ cập nhật',
|
|
446
|
+
'tôi sẽ cấu hình',
|
|
447
|
+
'tôi sẽ sửa',
|
|
448
|
+
'tôi sẽ tạo',
|
|
449
|
+
'tôi sẽ thêm',
|
|
450
|
+
'để tôi cập nhật',
|
|
451
|
+
'để tôi sửa',
|
|
287
452
|
]
|
|
288
453
|
|
|
289
454
|
// Indonesian: "saya akan" / "biar saya" (I will / let me) + work verb.
|
|
@@ -298,6 +463,16 @@ const ID_PHRASES: readonly string[] = [
|
|
|
298
463
|
'saya periksa dulu',
|
|
299
464
|
'tunggu sebentar',
|
|
300
465
|
'sebentar ya',
|
|
466
|
+
// Action/config verb family.
|
|
467
|
+
'saya akan perbarui',
|
|
468
|
+
'saya akan memperbarui',
|
|
469
|
+
'saya akan atur',
|
|
470
|
+
'saya akan konfigurasi',
|
|
471
|
+
'saya akan perbaiki',
|
|
472
|
+
'saya akan buat',
|
|
473
|
+
'saya akan tambah',
|
|
474
|
+
'biar saya perbaiki',
|
|
475
|
+
'biar saya perbarui',
|
|
301
476
|
]
|
|
302
477
|
|
|
303
478
|
const ALL_PHRASES: readonly string[] = [
|
|
@@ -318,6 +493,41 @@ const ALL_PHRASES: readonly string[] = [
|
|
|
318
493
|
...ID_PHRASES,
|
|
319
494
|
]
|
|
320
495
|
|
|
496
|
+
// First-person future/volitional realized as a verb inflection or affix (not a
|
|
497
|
+
// separate word), so matching the marker generalizes across ALL action verbs.
|
|
498
|
+
const MORPHEME_PATTERNS: readonly RegExp[] = [
|
|
499
|
+
// Korean first-person volitional: a verb stem + -겠습니다/-겠어요. The stem must
|
|
500
|
+
// be one of the action/auxiliary verbs 하 (the 하다 light verb behind every
|
|
501
|
+
// X하다 — 업데이트하겠습니다/반영하겠어요), 보 (살펴보겠습니다), 두 (반영해두겠습니다), or
|
|
502
|
+
// 놓 (해놓겠습니다). Anchoring on the verb stem is what keeps this self-directed:
|
|
503
|
+
// bare 겠 also matches adjective-stem CONJECTURE (좋겠어요 "that'd be nice",
|
|
504
|
+
// 괜찮겠어요 "must be fine") and the idioms 알겠/모르겠, none of which promise work.
|
|
505
|
+
// Listener-directed conjecture takes the honorific 시 (피곤하시겠어요 → 시겠, not
|
|
506
|
+
// 하겠), so it is excluded too.
|
|
507
|
+
/(?:하|보|두|놓)겠(?:습니다|어요)/,
|
|
508
|
+
// Turkish first-person-singular future "-acağım/-eceğim" ("I will VERB").
|
|
509
|
+
// Vowel harmony yields exactly these two suffixes; the y-buffer
|
|
510
|
+
// ("bekleyeceğim") leaves the suffix intact.
|
|
511
|
+
/acağım|eceğim/,
|
|
512
|
+
// Hindi first-person future "-ūṅgā/-ūṅgī" on any verb, covering all X-करना
|
|
513
|
+
// compounds ("अपडेट करूँगा"). Both nasal spellings (ँ U+0901 / ं U+0902) and
|
|
514
|
+
// both genders (ा/ी) are included.
|
|
515
|
+
/ू[ँं]ग[ाी]/,
|
|
516
|
+
]
|
|
517
|
+
|
|
518
|
+
// Japanese する-verb volitional します/いたします/してみます — covers the action class
|
|
519
|
+
// (更新します/設定します/対応します). Bare ます is the universal polite verb ending and
|
|
520
|
+
// far too broad to match, so this keys on します specifically. Two request/greeting
|
|
521
|
+
// idioms end in します without being work intent — お願い(いた)します ("please") and
|
|
522
|
+
// 失礼します ("excuse me") — and are stripped before the test so they don't fire.
|
|
523
|
+
// The (?![か??]) lookahead drops question forms — both the か question particle
|
|
524
|
+
// (どうしますか "what should I do?") and a trailing question mark, fullwidth ? or
|
|
525
|
+
// ASCII ? (どうします?, 更新します? "shall I update?"). A question awaits the user;
|
|
526
|
+
// it is not a commitment to act this turn. A statement keeps its 。/, so
|
|
527
|
+
// 更新します。 still matches.
|
|
528
|
+
const JA_VOLITIONAL_IDIOMS = /お願い(?:いた)?します|失礼します/g
|
|
529
|
+
const JA_VOLITIONAL = /します(?![か??])|してみます(?![か??])/
|
|
530
|
+
|
|
321
531
|
// Reply texts shorter than this are almost always a complete final answer
|
|
322
532
|
// ("네", "ok", "done") where a partial match would be noise. The shortest
|
|
323
533
|
// legitimate intent phrases ("on it now", "확인할게요") clear this floor.
|
|
@@ -327,5 +537,7 @@ export function detectContinuationWillingness(text: string): boolean {
|
|
|
327
537
|
if (text.length < MIN_LENGTH) return false
|
|
328
538
|
const normalized = normalize(text)
|
|
329
539
|
if (normalized.length < MIN_LENGTH) return false
|
|
330
|
-
|
|
540
|
+
if (ALL_PHRASES.some((phrase) => normalized.includes(phrase))) return true
|
|
541
|
+
if (MORPHEME_PATTERNS.some((pattern) => pattern.test(normalized))) return true
|
|
542
|
+
return JA_VOLITIONAL.test(normalized.replace(JA_VOLITIONAL_IDIOMS, ''))
|
|
331
543
|
}
|