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.
- package/auth.schema.json +66 -0
- package/cron.schema.json +26 -2
- package/package.json +1 -1
- package/secrets.schema.json +66 -0
- package/src/agent/index.ts +7 -3
- package/src/agent/session-origin.ts +17 -0
- package/src/agent/subagent-completion-reminder.ts +14 -1
- package/src/agent/subagent-drain.ts +2 -0
- package/src/agent/subagents.ts +21 -7
- package/src/agent/tools/channel-disengage.ts +66 -0
- package/src/agent/tools/channel-log.ts +3 -2
- package/src/agent/tools/spawn-subagent.ts +25 -5
- package/src/agent/tools/subagent-output.ts +13 -1
- package/src/bundled-plugins/guard/policies/managed-config.ts +1 -1
- package/src/bundled-plugins/memory/memory-logger.ts +7 -0
- package/src/bundled-plugins/researcher/researcher.ts +14 -11
- package/src/bundled-plugins/security/policies/outbound-secret-scan.ts +2 -0
- package/src/channels/adapters/line-channel-resolver.ts +129 -0
- package/src/channels/adapters/line-classify.ts +80 -0
- package/src/channels/adapters/line-format.ts +11 -0
- package/src/channels/adapters/line.ts +350 -0
- package/src/channels/engagement.ts +4 -2
- package/src/channels/manager.ts +65 -6
- package/src/channels/router.ts +186 -41
- package/src/channels/schema.ts +6 -1
- package/src/cli/channel.ts +112 -1
- package/src/cli/cron.ts +22 -4
- package/src/cli/init.ts +267 -82
- package/src/cli/model.ts +5 -1
- package/src/cli/oauth-callbacks.ts +5 -4
- package/src/cli/provider.ts +41 -10
- package/src/config/providers.ts +366 -7
- package/src/cron/consumer.ts +33 -0
- package/src/cron/count-state.ts +208 -0
- package/src/cron/index.ts +4 -17
- package/src/cron/list.ts +24 -6
- package/src/cron/scheduler.ts +84 -9
- package/src/cron/schema.ts +100 -13
- package/src/doctor/channel-checks.ts +28 -0
- package/src/hostd/daemon.ts +14 -6
- package/src/hostd/protocol.ts +6 -2
- package/src/init/gitignore.ts +1 -1
- package/src/init/index.ts +36 -3
- package/src/init/line-auth.ts +98 -0
- package/src/init/models-dev.ts +3 -0
- package/src/init/run-owner-claim.ts +1 -0
- package/src/init/validate-api-key.ts +15 -0
- package/src/inspect/label.ts +1 -0
- package/src/permissions/match-rule.ts +28 -12
- package/src/permissions/resolve.ts +8 -1
- package/src/role-claim/match-rule.ts +5 -1
- package/src/run/index.ts +41 -4
- package/src/secrets/line-store.ts +112 -0
- package/src/secrets/oauth-xai.ts +342 -0
- package/src/secrets/schema.ts +25 -0
- package/src/secrets/storage.ts +2 -0
- package/src/server/index.ts +17 -4
- package/src/shared/protocol.ts +4 -1
- package/src/skills/typeclaw-channel-line/SKILL.md +46 -0
- package/src/skills/typeclaw-channels/SKILL.md +153 -0
- package/src/skills/typeclaw-config/SKILL.md +54 -184
- package/src/skills/typeclaw-config/references/dockerfile.md +66 -0
- package/src/skills/typeclaw-cron/SKILL.md +68 -14
- package/src/skills/typeclaw-permissions/SKILL.md +3 -3
- package/typeclaw.schema.json +185 -3
package/src/cli/channel.ts
CHANGED
|
@@ -22,8 +22,10 @@ import {
|
|
|
22
22
|
type GithubCredentialPatch,
|
|
23
23
|
type GithubTunnelProvider,
|
|
24
24
|
type KakaotalkAuthResult,
|
|
25
|
+
type LineAuthResult,
|
|
25
26
|
} from '@/init'
|
|
26
27
|
import { runKakaotalkBootstrap } from '@/init/kakaotalk-auth'
|
|
28
|
+
import { runLineBootstrap } from '@/init/line-auth'
|
|
27
29
|
import { SecretsKakaoCredentialStore } from '@/secrets/kakao-store'
|
|
28
30
|
|
|
29
31
|
import { CANCEL_SYMBOL, promptPrivateKeyPem } from './prompt-pem'
|
|
@@ -33,6 +35,7 @@ const CHANNEL_LABELS: Record<ChannelKind, string> = {
|
|
|
33
35
|
'slack-bot': 'Slack',
|
|
34
36
|
'discord-bot': 'Discord',
|
|
35
37
|
'telegram-bot': 'Telegram',
|
|
38
|
+
line: 'LINE',
|
|
36
39
|
kakaotalk: 'KakaoTalk',
|
|
37
40
|
github: 'GitHub',
|
|
38
41
|
}
|
|
@@ -127,6 +130,15 @@ const setSub = defineCommand({
|
|
|
127
130
|
process.exit(1)
|
|
128
131
|
}
|
|
129
132
|
|
|
133
|
+
if (args.adapter === 'line') {
|
|
134
|
+
console.error(
|
|
135
|
+
errorLine(
|
|
136
|
+
'LINE uses an interactive auth flow (QR or email + PIN). Use `typeclaw channel reauth line` to rotate its credentials.',
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
process.exit(1)
|
|
140
|
+
}
|
|
141
|
+
|
|
130
142
|
const adapter =
|
|
131
143
|
args.adapter === undefined
|
|
132
144
|
? await pickSettableAdapter(configured)
|
|
@@ -138,7 +150,7 @@ const setSub = defineCommand({
|
|
|
138
150
|
},
|
|
139
151
|
})
|
|
140
152
|
|
|
141
|
-
const REAUTHABLE_ADAPTERS = ['kakaotalk'] as const
|
|
153
|
+
const REAUTHABLE_ADAPTERS = ['line', 'kakaotalk'] as const
|
|
142
154
|
type ReauthableAdapter = (typeof REAUTHABLE_ADAPTERS)[number]
|
|
143
155
|
|
|
144
156
|
const reauthSub = defineCommand({
|
|
@@ -234,12 +246,29 @@ function isReauthableAdapter(value: string): value is ReauthableAdapter {
|
|
|
234
246
|
|
|
235
247
|
async function runReauth(cwd: string, adapter: ReauthableAdapter): Promise<void> {
|
|
236
248
|
switch (adapter) {
|
|
249
|
+
case 'line':
|
|
250
|
+
await runLineReauth(cwd)
|
|
251
|
+
return
|
|
237
252
|
case 'kakaotalk':
|
|
238
253
|
await runKakaotalkReauth(cwd)
|
|
239
254
|
return
|
|
240
255
|
}
|
|
241
256
|
}
|
|
242
257
|
|
|
258
|
+
async function runLineReauth(cwd: string): Promise<void> {
|
|
259
|
+
const login = await promptLineLogin()
|
|
260
|
+
const s = spinner()
|
|
261
|
+
s.start('Logging in to LINE...')
|
|
262
|
+
const result = await runLineBootstrap({ ...login, agentDir: cwd })
|
|
263
|
+
if (!result.ok) {
|
|
264
|
+
s.stop(`LINE login failed: ${result.reason}`)
|
|
265
|
+
process.exit(1)
|
|
266
|
+
}
|
|
267
|
+
s.stop('LINE credentials refreshed in secrets.json.')
|
|
268
|
+
|
|
269
|
+
await maybePromptReauthRefresh(cwd, 'line')
|
|
270
|
+
}
|
|
271
|
+
|
|
243
272
|
async function runKakaotalkReauth(cwd: string): Promise<void> {
|
|
244
273
|
const existingEmail = await readExistingKakaotalkEmail(cwd)
|
|
245
274
|
const creds = await promptKakaotalkCredentials({ defaultEmail: existingEmail })
|
|
@@ -566,6 +595,7 @@ type CollectedCredentials =
|
|
|
566
595
|
| { channel: 'discord-bot'; discordBotToken: string }
|
|
567
596
|
| { channel: 'slack-bot'; slackBotToken: string; slackAppToken: string }
|
|
568
597
|
| { channel: 'telegram-bot'; telegramBotToken: string }
|
|
598
|
+
| { channel: 'line'; runLineAuth: (options: { cwd: string }) => Promise<LineAuthResult> }
|
|
569
599
|
| { channel: 'kakaotalk'; runKakaotalkAuth: (options: { cwd: string }) => Promise<KakaotalkAuthResult> }
|
|
570
600
|
| {
|
|
571
601
|
channel: 'github'
|
|
@@ -587,6 +617,13 @@ async function collectCredentials(channel: ChannelKind, cwd: string): Promise<Co
|
|
|
587
617
|
}
|
|
588
618
|
case 'telegram-bot':
|
|
589
619
|
return { channel, telegramBotToken: await promptTelegramToken() }
|
|
620
|
+
case 'line': {
|
|
621
|
+
const login = await promptLineLogin()
|
|
622
|
+
return {
|
|
623
|
+
channel,
|
|
624
|
+
runLineAuth: ({ cwd: agentDir }) => runLineBootstrap({ ...login, agentDir }),
|
|
625
|
+
}
|
|
626
|
+
}
|
|
590
627
|
case 'kakaotalk': {
|
|
591
628
|
const creds = await promptKakaotalkCredentials()
|
|
592
629
|
return {
|
|
@@ -1023,6 +1060,71 @@ async function promptKakaotalkCredentials(
|
|
|
1023
1060
|
return { email, password: pwd }
|
|
1024
1061
|
}
|
|
1025
1062
|
|
|
1063
|
+
type LinePromptResult =
|
|
1064
|
+
| { method: 'qr'; callbacks: { onQRUrl: (url: string) => void; onPincode: (pin: string) => void } }
|
|
1065
|
+
| {
|
|
1066
|
+
method: 'email'
|
|
1067
|
+
email: string
|
|
1068
|
+
password: string
|
|
1069
|
+
callbacks: { onPincode: (pin: string) => void }
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
async function promptLineLogin(): Promise<LinePromptResult> {
|
|
1073
|
+
note(
|
|
1074
|
+
[
|
|
1075
|
+
'LINE authentication uses a personal account registered as a sub-device.',
|
|
1076
|
+
'Messages will be sent and received under this account — use a',
|
|
1077
|
+
'non-primary account if possible.',
|
|
1078
|
+
'',
|
|
1079
|
+
'QR login is recommended: it works even when the account has no',
|
|
1080
|
+
'email/password set (social-login accounts).',
|
|
1081
|
+
].join('\n'),
|
|
1082
|
+
'About to log in to LINE',
|
|
1083
|
+
)
|
|
1084
|
+
const method = await select<'qr' | 'email'>({
|
|
1085
|
+
message: 'How do you want to log in to LINE?',
|
|
1086
|
+
options: [
|
|
1087
|
+
{ value: 'qr', label: 'QR code — scan with the LINE app on your phone (recommended)' },
|
|
1088
|
+
{ value: 'email', label: 'Email + password — for accounts with email login enabled' },
|
|
1089
|
+
],
|
|
1090
|
+
initialValue: 'qr',
|
|
1091
|
+
})
|
|
1092
|
+
if (isCancel(method)) {
|
|
1093
|
+
cancel('Aborted.')
|
|
1094
|
+
process.exit(0)
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
const onPincode = (pin: string): void => log.info(`Enter this PIN in the LINE app to confirm: ${pin}`)
|
|
1098
|
+
|
|
1099
|
+
if (method === 'qr') {
|
|
1100
|
+
return {
|
|
1101
|
+
method: 'qr',
|
|
1102
|
+
callbacks: {
|
|
1103
|
+
onQRUrl: (url) => note(url, 'Open this URL on your phone (or scan the QR it renders)'),
|
|
1104
|
+
onPincode,
|
|
1105
|
+
},
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
const email = await text({
|
|
1110
|
+
message: 'LINE email',
|
|
1111
|
+
validate: (value) => (value && value.length > 0 ? undefined : 'Email is required'),
|
|
1112
|
+
})
|
|
1113
|
+
if (isCancel(email)) {
|
|
1114
|
+
cancel('Aborted.')
|
|
1115
|
+
process.exit(0)
|
|
1116
|
+
}
|
|
1117
|
+
const pwd = await password({
|
|
1118
|
+
message: 'LINE password',
|
|
1119
|
+
validate: (value) => (value && value.length > 0 ? undefined : 'Password is required'),
|
|
1120
|
+
})
|
|
1121
|
+
if (isCancel(pwd)) {
|
|
1122
|
+
cancel('Aborted.')
|
|
1123
|
+
process.exit(0)
|
|
1124
|
+
}
|
|
1125
|
+
return { method: 'email', email, password: pwd, callbacks: { onPincode } }
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1026
1128
|
function reportProgress(events: AddChannelStepEvent[]): (event: AddChannelStepEvent) => void {
|
|
1027
1129
|
const spinners: Partial<Record<AddChannelStepEvent['step'], ReturnType<typeof spinner>>> = {}
|
|
1028
1130
|
|
|
@@ -1039,6 +1141,9 @@ function reportProgress(events: AddChannelStepEvent[]): (event: AddChannelStepEv
|
|
|
1039
1141
|
if (!s) return
|
|
1040
1142
|
|
|
1041
1143
|
switch (event.step) {
|
|
1144
|
+
case 'line-auth':
|
|
1145
|
+
s.stop(reportLineAuth(event.result))
|
|
1146
|
+
break
|
|
1042
1147
|
case 'kakaotalk-auth':
|
|
1043
1148
|
s.stop(reportKakaotalkAuth(event.result))
|
|
1044
1149
|
break
|
|
@@ -1056,6 +1161,7 @@ function reportProgress(events: AddChannelStepEvent[]): (event: AddChannelStepEv
|
|
|
1056
1161
|
}
|
|
1057
1162
|
|
|
1058
1163
|
const START_MESSAGES: Record<AddChannelStepEvent['step'], string> = {
|
|
1164
|
+
'line-auth': 'Logging in to LINE...',
|
|
1059
1165
|
'kakaotalk-auth': 'Logging in to KakaoTalk...',
|
|
1060
1166
|
config: 'Updating typeclaw.json...',
|
|
1061
1167
|
secrets: 'Saving credentials to secrets.json...',
|
|
@@ -1067,6 +1173,11 @@ function reportKakaotalkAuth(result: KakaotalkAuthResult): string {
|
|
|
1067
1173
|
return `KakaoTalk login failed: ${result.reason}`
|
|
1068
1174
|
}
|
|
1069
1175
|
|
|
1176
|
+
function reportLineAuth(result: LineAuthResult): string {
|
|
1177
|
+
if (result.ok) return 'LINE credentials saved to secrets.json.'
|
|
1178
|
+
return `LINE login failed: ${result.reason}`
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1070
1181
|
async function maybePromptRestart(cwd: string, channel: ChannelKind): Promise<void> {
|
|
1071
1182
|
const label = CHANNEL_LABELS[channel]
|
|
1072
1183
|
const current = await status({ cwd }).catch(() => null)
|
package/src/cli/cron.ts
CHANGED
|
@@ -114,12 +114,30 @@ function formatEntry(job: CronListEntryPayload, nowMs: number): string {
|
|
|
114
114
|
const kindBadge = c.dim(`[${job.kind}]`)
|
|
115
115
|
lines.push(`${c.bold(displayId(job))} ${kindBadge} ${sourceLabel}${enabledBadge}`)
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
if (job.at !== undefined) {
|
|
118
|
+
lines.push(` ${c.dim('at ')} ${job.at}`)
|
|
119
|
+
} else {
|
|
120
|
+
const tz = job.timezone !== undefined ? ` ${c.dim(`(${job.timezone})`)}` : ''
|
|
121
|
+
lines.push(` ${c.dim('schedule')} ${job.schedule}${tz}`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const stops: string[] = []
|
|
125
|
+
if (job.until !== undefined) stops.push(`until ${job.until}`)
|
|
126
|
+
if (job.count !== undefined) stops.push(`count ${job.count}`)
|
|
127
|
+
if (stops.length > 0) {
|
|
128
|
+
lines.push(` ${c.dim('stops ')} ${stops.join(', ')}`)
|
|
129
|
+
}
|
|
119
130
|
|
|
120
131
|
if (job.nextFireMs === null) {
|
|
121
|
-
|
|
122
|
-
|
|
132
|
+
// A null next-fire means one of two very different things. A parse error
|
|
133
|
+
// carries `scheduleError` and is a problem to surface in red. No error
|
|
134
|
+
// means the job hit its count/until boundary and is simply retired — not
|
|
135
|
+
// broken — so it must not be mislabeled as an invalid schedule.
|
|
136
|
+
if (job.scheduleError !== undefined) {
|
|
137
|
+
lines.push(` ${c.dim('next ')} ${c.red('invalid schedule')}: ${job.scheduleError}`)
|
|
138
|
+
} else {
|
|
139
|
+
lines.push(` ${c.dim('next ')} ${c.dim('retired (boundary reached)')}`)
|
|
140
|
+
}
|
|
123
141
|
} else {
|
|
124
142
|
lines.push(` ${c.dim('next ')} ${formatNextFire(job.nextFireMs, nowMs)}`)
|
|
125
143
|
}
|