typeclaw 0.3.1 → 0.4.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/README.md +20 -15
- package/auth.schema.json +113 -0
- package/package.json +1 -1
- package/secrets.schema.json +113 -0
- package/src/agent/session-meta.ts +1 -1
- package/src/agent/session-origin.ts +3 -2
- package/src/bundled-plugins/security/index.ts +3 -2
- package/src/channels/adapters/github/auth-app.ts +120 -0
- package/src/channels/adapters/github/auth-pat.ts +50 -0
- package/src/channels/adapters/github/auth.ts +33 -0
- package/src/channels/adapters/github/channel-resolver.ts +30 -0
- package/src/channels/adapters/github/dedup.ts +26 -0
- package/src/channels/adapters/github/event-allowlist.ts +8 -0
- package/src/channels/adapters/github/fetch-attachment.ts +5 -0
- package/src/channels/adapters/github/history.ts +63 -0
- package/src/channels/adapters/github/inbound.ts +286 -0
- package/src/channels/adapters/github/index.ts +286 -0
- package/src/channels/adapters/github/managed-path.ts +54 -0
- package/src/channels/adapters/github/membership.ts +35 -0
- package/src/channels/adapters/github/outbound.ts +145 -0
- package/src/channels/adapters/github/webhook-register.ts +349 -0
- package/src/channels/manager.ts +94 -9
- package/src/channels/schema.ts +31 -1
- package/src/channels/tunnel-bridge.ts +51 -0
- package/src/cli/builtins.ts +28 -0
- package/src/cli/channel.ts +511 -25
- package/src/cli/container-command-client.ts +244 -0
- package/src/cli/cron.ts +173 -0
- package/src/cli/host-command-runner.ts +150 -0
- package/src/cli/index.ts +42 -1
- package/src/cli/init.ts +256 -27
- package/src/cli/model.ts +4 -2
- package/src/cli/plugin-command-help.ts +49 -0
- package/src/cli/plugin-commands-dispatch.ts +112 -0
- package/src/cli/plugin-commands.ts +118 -0
- package/src/cli/tui.ts +10 -2
- package/src/cli/tunnel.ts +533 -0
- package/src/cli/ui.ts +8 -3
- package/src/config/config.ts +75 -0
- package/src/container/start.ts +30 -3
- package/src/cron/bridge.ts +136 -0
- package/src/cron/consumer.ts +45 -5
- package/src/cron/index.ts +19 -2
- package/src/cron/list.ts +105 -0
- package/src/cron/scheduler.ts +12 -3
- package/src/cron/schema.ts +11 -3
- package/src/doctor/checks.ts +0 -50
- package/src/init/dockerfile.ts +59 -13
- package/src/init/ensure-deps.ts +15 -4
- package/src/init/github-webhook-install.ts +109 -0
- package/src/init/index.ts +505 -9
- package/src/init/run-bun-install.ts +17 -3
- package/src/init/run-owner-claim.ts +11 -2
- package/src/permissions/builtins.ts +6 -1
- package/src/permissions/match-rule.ts +24 -2
- package/src/permissions/resolve.ts +1 -0
- package/src/plugin/define.ts +42 -1
- package/src/plugin/index.ts +18 -3
- package/src/plugin/manager.ts +2 -0
- package/src/plugin/registry.ts +85 -3
- package/src/plugin/types.ts +138 -1
- package/src/plugin/zod-introspect.ts +100 -0
- package/src/role-claim/match-rule.ts +2 -1
- package/src/run/index.ts +110 -3
- package/src/secrets/index.ts +1 -1
- package/src/secrets/schema.ts +21 -0
- package/src/server/command-runner.ts +476 -0
- package/src/server/index.ts +388 -5
- package/src/shared/index.ts +8 -0
- package/src/shared/protocol.ts +80 -1
- package/src/skills/typeclaw-channel-github/SKILL.md +24 -0
- package/src/skills/typeclaw-config/SKILL.md +27 -26
- package/src/skills/typeclaw-cron/SKILL.md +234 -3
- package/src/skills/typeclaw-monorepo/SKILL.md +2 -2
- package/src/skills/typeclaw-permissions/SKILL.md +5 -4
- package/src/skills/typeclaw-plugins/SKILL.md +251 -5
- package/src/skills/typeclaw-tunnels/SKILL.md +111 -0
- package/src/test-helpers/wait-for.ts +50 -0
- package/src/tui/index.ts +35 -4
- package/src/tunnels/__fixtures__/cloudflared-quick-stderr.txt +11 -0
- package/src/tunnels/events.ts +14 -0
- package/src/tunnels/index.ts +12 -0
- package/src/tunnels/log-ring.ts +54 -0
- package/src/tunnels/manager.ts +139 -0
- package/src/tunnels/providers/cloudflare-quick.ts +189 -0
- package/src/tunnels/providers/external.ts +53 -0
- package/src/tunnels/quick-url-parser.ts +5 -0
- package/src/tunnels/types.ts +43 -0
- package/typeclaw.schema.json +254 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export type LeafKind = 'string' | 'number' | 'boolean' | 'unknown'
|
|
4
|
+
|
|
5
|
+
export type LeafDescription = {
|
|
6
|
+
kind: LeafKind
|
|
7
|
+
required: boolean
|
|
8
|
+
defaultValue: string | undefined
|
|
9
|
+
description: string | undefined
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Walks the chain of Zod 4 wrappers (optional, default, nullable) and returns
|
|
13
|
+
// the inner-most leaf node. Reads `_def.type` (Zod 4's lowercase discriminator)
|
|
14
|
+
// directly because the public `instanceof ZodOptional` checks don't work for
|
|
15
|
+
// `.innerType` — Zod 4 types it as the base `$ZodType`, not the public class
|
|
16
|
+
// hierarchy. If Zod ships a breaking change to `_def.type`, this is the only
|
|
17
|
+
// file that needs to update.
|
|
18
|
+
export function describeLeaf(leaf: unknown): LeafDescription {
|
|
19
|
+
let cur: unknown = leaf
|
|
20
|
+
let required = true
|
|
21
|
+
let defaultValue: string | undefined
|
|
22
|
+
let description: string | undefined
|
|
23
|
+
|
|
24
|
+
while (cur !== null && typeof cur === 'object') {
|
|
25
|
+
const node = cur as {
|
|
26
|
+
_def?: { type?: string; innerType?: unknown; defaultValue?: unknown }
|
|
27
|
+
description?: string
|
|
28
|
+
}
|
|
29
|
+
if (typeof node.description === 'string') description = node.description
|
|
30
|
+
const def = node._def
|
|
31
|
+
if (def === undefined) break
|
|
32
|
+
if (def.type === 'optional') {
|
|
33
|
+
required = false
|
|
34
|
+
cur = def.innerType
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
if (def.type === 'default') {
|
|
38
|
+
required = false
|
|
39
|
+
const raw = typeof def.defaultValue === 'function' ? (def.defaultValue as () => unknown)() : def.defaultValue
|
|
40
|
+
defaultValue = raw === undefined ? undefined : JSON.stringify(raw)
|
|
41
|
+
cur = def.innerType
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
if (def.type === 'nullable') {
|
|
45
|
+
cur = def.innerType
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
return { kind: classify(def.type), required, defaultValue, description }
|
|
49
|
+
}
|
|
50
|
+
return { kind: 'unknown', required, defaultValue, description }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function classify(t: string | undefined): LeafKind {
|
|
54
|
+
switch (t) {
|
|
55
|
+
case 'string':
|
|
56
|
+
case 'literal':
|
|
57
|
+
case 'enum':
|
|
58
|
+
return 'string'
|
|
59
|
+
case 'number':
|
|
60
|
+
case 'int':
|
|
61
|
+
return 'number'
|
|
62
|
+
case 'boolean':
|
|
63
|
+
return 'boolean'
|
|
64
|
+
default:
|
|
65
|
+
return 'unknown'
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Coerces a single CLI flag value (string from argv or `true` when bare) to the
|
|
70
|
+
// type the leaf expects. Throws a precise error referencing the flag key when
|
|
71
|
+
// coercion fails; the caller surfaces the message to stderr.
|
|
72
|
+
export function coerceFlag(leaf: unknown, raw: string | true, key: string): unknown {
|
|
73
|
+
const info = describeLeaf(leaf)
|
|
74
|
+
if (info.kind === 'boolean') {
|
|
75
|
+
if (raw === true || raw === 'true') return true
|
|
76
|
+
if (raw === 'false') return false
|
|
77
|
+
throw new Error(`--${key}: expected true/false, got "${raw}"`)
|
|
78
|
+
}
|
|
79
|
+
if (info.kind === 'number') {
|
|
80
|
+
if (raw === true) throw new Error(`--${key} requires a numeric value`)
|
|
81
|
+
if (raw === '') throw new Error(`--${key}: empty value rejected; pass a number`)
|
|
82
|
+
const n = Number(raw)
|
|
83
|
+
if (Number.isNaN(n)) throw new Error(`--${key}: not a number: "${raw}"`)
|
|
84
|
+
return n
|
|
85
|
+
}
|
|
86
|
+
if (raw === true) throw new Error(`--${key} requires a value`)
|
|
87
|
+
return raw
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Returns true when `schema` is a Zod 4 z.object whose leaf properties are all
|
|
91
|
+
// primitive-shaped (string/number/boolean, with optional/default/nullable
|
|
92
|
+
// wrappers). Plugin command args schemas MUST satisfy this in v1.
|
|
93
|
+
export function isPrimitiveZodObject(schema: unknown): schema is z.ZodObject<z.ZodRawShape> {
|
|
94
|
+
if (!(schema instanceof z.ZodObject)) return false
|
|
95
|
+
const shape = (schema as z.ZodObject<z.ZodRawShape>).shape as Record<string, unknown>
|
|
96
|
+
for (const leaf of Object.values(shape)) {
|
|
97
|
+
if (describeLeaf(leaf).kind === 'unknown') return false
|
|
98
|
+
}
|
|
99
|
+
return true
|
|
100
|
+
}
|
|
@@ -21,9 +21,10 @@ export type PartialChannelOrigin = {
|
|
|
21
21
|
authorId: string
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
const ADAPTER_TO_PLATFORM: Record<ChannelKey['adapter'], 'slack' | 'discord' | 'telegram' | 'kakao'> = {
|
|
24
|
+
const ADAPTER_TO_PLATFORM: Record<ChannelKey['adapter'], 'slack' | 'discord' | 'telegram' | 'kakao' | 'github'> = {
|
|
25
25
|
'slack-bot': 'slack',
|
|
26
26
|
'discord-bot': 'discord',
|
|
27
|
+
github: 'github',
|
|
27
28
|
'telegram-bot': 'telegram',
|
|
28
29
|
kakaotalk: 'kakao',
|
|
29
30
|
}
|
package/src/run/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from '@/agent/subagents'
|
|
13
13
|
import { resolveCapOptionsFromConfig } from '@/bundled-plugins/tool-result-cap'
|
|
14
14
|
import { createChannelManager, createChannelsReloadable, type ChannelManager } from '@/channels'
|
|
15
|
+
import { createTunnelBridge, type TunnelBridge } from '@/channels/tunnel-bridge'
|
|
15
16
|
import { createConfigReloadable, getConfig, loadConfigSync, loadPluginConfigsSync } from '@/config'
|
|
16
17
|
import {
|
|
17
18
|
type CronConsumer,
|
|
@@ -26,14 +27,24 @@ import {
|
|
|
26
27
|
} from '@/cron'
|
|
27
28
|
import { CLI_VERSION } from '@/init/cli-version'
|
|
28
29
|
import { loadPlugins, type LoadPluginsResult, pluginCronJobs, type PluginRegistry, summarizeLoaded } from '@/plugin'
|
|
30
|
+
import { createPluginLogger } from '@/plugin/context'
|
|
31
|
+
import type { CronHandlerContext } from '@/plugin/types'
|
|
29
32
|
import { createContainerBroker, publishForwardResult } from '@/portbroker'
|
|
30
33
|
import { ReloadRegistry } from '@/reload'
|
|
31
34
|
import { createClaimController } from '@/role-claim'
|
|
32
35
|
import { hydrateChannelEnvFromSecrets } from '@/secrets'
|
|
33
36
|
import { createServer, type Server } from '@/server'
|
|
37
|
+
import {
|
|
38
|
+
createCommandRunner,
|
|
39
|
+
type CommandRunner,
|
|
40
|
+
type CommandSpawnSubagent,
|
|
41
|
+
runExecForCommand,
|
|
42
|
+
runPromptForCommand,
|
|
43
|
+
} from '@/server/command-runner'
|
|
34
44
|
import { createSessionFactory, type SessionFactory } from '@/sessions'
|
|
35
45
|
import { createStream, type Stream } from '@/stream'
|
|
36
46
|
import { createTui as createTuiDefault, type TuiOptions } from '@/tui'
|
|
47
|
+
import { createTunnelManager, type TunnelManager, type TunnelManagerOptions } from '@/tunnels'
|
|
37
48
|
|
|
38
49
|
import { BUNDLED_PLUGINS } from './bundled-plugins'
|
|
39
50
|
import { buildChannelSessionFactory } from './channel-session-factory'
|
|
@@ -45,6 +56,8 @@ export type TuiFactory = (options: TuiOptions) => { run: () => Promise<void> }
|
|
|
45
56
|
|
|
46
57
|
export type LoadCronFn = (agentDir: string, options?: { subagents?: SubagentRegistry }) => Promise<LoadCronResult>
|
|
47
58
|
export type SchedulerFactory = (options: { cwd: string; file: CronFile; onFire: (job: CronJob) => void }) => Scheduler
|
|
59
|
+
export type ChannelManagerFactory = typeof createChannelManager
|
|
60
|
+
export type TunnelManagerFactory = (options: TunnelManagerOptions) => TunnelManager
|
|
48
61
|
|
|
49
62
|
export type StartAgentOptions = {
|
|
50
63
|
port: number
|
|
@@ -56,6 +69,8 @@ export type StartAgentOptions = {
|
|
|
56
69
|
createSchedulerFor?: SchedulerFactory
|
|
57
70
|
sessionFactory?: SessionFactory
|
|
58
71
|
stream?: Stream
|
|
72
|
+
createChannelManager?: ChannelManagerFactory
|
|
73
|
+
createTunnelManager?: TunnelManagerFactory
|
|
59
74
|
}
|
|
60
75
|
|
|
61
76
|
export type StartAgentResult = {
|
|
@@ -82,6 +97,8 @@ export async function startAgent({
|
|
|
82
97
|
createSchedulerFor,
|
|
83
98
|
sessionFactory = createSessionFactory({ agentDir: cwd }),
|
|
84
99
|
stream = createStream(),
|
|
100
|
+
createChannelManager: createChannelManagerFor = createChannelManager,
|
|
101
|
+
createTunnelManager: createTunnelManagerFor = createTunnelManager,
|
|
85
102
|
}: StartAgentOptions): Promise<StartAgentResult> {
|
|
86
103
|
const reloadRegistry = new ReloadRegistry()
|
|
87
104
|
|
|
@@ -123,6 +140,7 @@ export async function startAgent({
|
|
|
123
140
|
pluginRegistry.cronJobs.length > 0 ||
|
|
124
141
|
pluginRegistry.skills.length > 0 ||
|
|
125
142
|
pluginRegistry.skillsDirs.length > 0 ||
|
|
143
|
+
pluginRegistry.commands.length > 0 ||
|
|
126
144
|
pluginsLoaded.loadedPlugins.length > 0
|
|
127
145
|
|
|
128
146
|
const pluginRuntime = createPluginRuntime({
|
|
@@ -149,10 +167,21 @@ export async function startAgent({
|
|
|
149
167
|
rolesProvider: () => getConfig().roles,
|
|
150
168
|
})
|
|
151
169
|
|
|
152
|
-
const
|
|
170
|
+
const tunnelManager: TunnelManager = createTunnelManagerFor({
|
|
171
|
+
tunnels: getConfig().tunnels,
|
|
172
|
+
stream,
|
|
173
|
+
resolveChannelUpstreamPort: (name) => {
|
|
174
|
+
if (name === 'github') return getConfig().channels.github?.webhookPort ?? null
|
|
175
|
+
return null
|
|
176
|
+
},
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
const channelManager = createChannelManagerFor({
|
|
153
180
|
agentDir: cwd,
|
|
154
181
|
channelsConfigRef: () => getConfig().channels,
|
|
155
182
|
aliasesRef: () => getConfig().alias,
|
|
183
|
+
tunnelUrlForChannel: (name) => resolveTunnelUrlForChannel(name, tunnelManager),
|
|
184
|
+
tunnelConfiguredForChannel: (name) => isTunnelConfiguredForChannel(name),
|
|
156
185
|
createSessionForChannel: buildChannelSessionFactory({
|
|
157
186
|
cwd,
|
|
158
187
|
sessionFactory,
|
|
@@ -244,6 +273,47 @@ export async function startAgent({
|
|
|
244
273
|
const cronConsumer = createCronConsumer({
|
|
245
274
|
stream,
|
|
246
275
|
cwd,
|
|
276
|
+
invokeHandler: async (job) => {
|
|
277
|
+
const snap = pluginRuntime.get()
|
|
278
|
+
const registered = snap.registry.cronJobs.find((j) => j.globalId === job.id)
|
|
279
|
+
const pluginName = registered?.pluginName ?? '<unknown>'
|
|
280
|
+
const logger = createPluginLogger(pluginName)
|
|
281
|
+
const abortController = new AbortController()
|
|
282
|
+
const origin: SessionOrigin = {
|
|
283
|
+
kind: 'cron',
|
|
284
|
+
jobId: job.id,
|
|
285
|
+
jobKind: 'handler',
|
|
286
|
+
...(job.scheduledByRole !== undefined ? { scheduledByRole: job.scheduledByRole } : {}),
|
|
287
|
+
scheduledByOrigin: (job.scheduledByOrigin as SessionOrigin | undefined) ?? { kind: 'config-file' },
|
|
288
|
+
}
|
|
289
|
+
const ctx: CronHandlerContext = {
|
|
290
|
+
jobId: job.id,
|
|
291
|
+
name: pluginName,
|
|
292
|
+
agentDir: cwd,
|
|
293
|
+
logger,
|
|
294
|
+
signal: abortController.signal,
|
|
295
|
+
permissions: pluginsLoaded.permissions,
|
|
296
|
+
origin,
|
|
297
|
+
prompt: (text: string) =>
|
|
298
|
+
runPromptForCommand({
|
|
299
|
+
text,
|
|
300
|
+
origin,
|
|
301
|
+
runtime: pluginRuntime,
|
|
302
|
+
agentDir: cwd,
|
|
303
|
+
permissions: pluginsLoaded.permissions,
|
|
304
|
+
signal: abortController.signal,
|
|
305
|
+
runtimeVersion: runtimeVersionOpt.runtimeVersion,
|
|
306
|
+
containerName: containerNameOpt.containerName,
|
|
307
|
+
}),
|
|
308
|
+
subagent: (subName: string, payload?: unknown) =>
|
|
309
|
+
dispatchSpawnSubagent(subName, payload, {
|
|
310
|
+
spawnedByOrigin: origin,
|
|
311
|
+
}),
|
|
312
|
+
exec: (strings: TemplateStringsArray, ...values: unknown[]) =>
|
|
313
|
+
runExecForCommand(strings, values, { cwd, signal: abortController.signal }),
|
|
314
|
+
}
|
|
315
|
+
await job.handler(ctx)
|
|
316
|
+
},
|
|
247
317
|
createSessionForCron: async (job) => {
|
|
248
318
|
const snap = pluginRuntime.get()
|
|
249
319
|
const sessionManager = SessionManager.create(cwd, sessionFactory.sessionDir())
|
|
@@ -310,10 +380,15 @@ export async function startAgent({
|
|
|
310
380
|
)
|
|
311
381
|
}
|
|
312
382
|
|
|
383
|
+
const tunnelBridge: TunnelBridge = createTunnelBridge({ stream, channelManager })
|
|
384
|
+
|
|
313
385
|
reloadRegistry.register(createChannelsReloadable({ manager: channelManager }))
|
|
314
386
|
await channelManager.start()
|
|
315
387
|
|
|
316
|
-
|
|
388
|
+
// Captured separately from setSpawnSubagent so both the plugin context and
|
|
389
|
+
// the plugin-command runner can dispatch through the same path. The setter
|
|
390
|
+
// returns void, so without this local binding we couldn't reuse the fn.
|
|
391
|
+
const dispatchSpawnSubagent: CommandSpawnSubagent = async (name, payload, options) => {
|
|
317
392
|
// Resolve the spawning session's role from its origin so the subagent
|
|
318
393
|
// inherits it. Callers (hooks like session.idle) pass the parent origin
|
|
319
394
|
// verbatim; we look up the role rather than letting the caller forge it,
|
|
@@ -333,7 +408,8 @@ export async function startAgent({
|
|
|
333
408
|
...(spawnedByRole !== undefined ? { spawnedByRole } : {}),
|
|
334
409
|
...(options?.spawnedByOrigin !== undefined ? { spawnedByOrigin: options.spawnedByOrigin } : {}),
|
|
335
410
|
})
|
|
336
|
-
}
|
|
411
|
+
}
|
|
412
|
+
pluginsLoaded.setSpawnSubagent(dispatchSpawnSubagent)
|
|
337
413
|
pluginsLoaded.markBooted()
|
|
338
414
|
|
|
339
415
|
if (pluginsLoaded.loadedPlugins.length > 0) {
|
|
@@ -365,6 +441,17 @@ export async function startAgent({
|
|
|
365
441
|
: undefined
|
|
366
442
|
const containerBrokerOpt = containerBroker ? { containerBroker } : {}
|
|
367
443
|
|
|
444
|
+
const commandRunnerFactory = (outbound: import('@/server/command-runner').CommandOutbound): CommandRunner =>
|
|
445
|
+
createCommandRunner({
|
|
446
|
+
pluginRuntime,
|
|
447
|
+
permissions: pluginsLoaded.permissions,
|
|
448
|
+
spawnSubagent: dispatchSpawnSubagent,
|
|
449
|
+
agentDir: cwd,
|
|
450
|
+
runtimeVersion: CLI_VERSION,
|
|
451
|
+
containerName,
|
|
452
|
+
outbound,
|
|
453
|
+
})
|
|
454
|
+
|
|
368
455
|
const server = createServer({
|
|
369
456
|
port,
|
|
370
457
|
reloadAll: () => reloadRegistry.reloadAll(),
|
|
@@ -375,12 +462,21 @@ export async function startAgent({
|
|
|
375
462
|
agentDir: cwd,
|
|
376
463
|
pluginRuntime,
|
|
377
464
|
claimController,
|
|
465
|
+
commandRunnerFactory,
|
|
466
|
+
tunnelManager,
|
|
378
467
|
...containerNameOpt,
|
|
379
468
|
...runtimeVersionOpt,
|
|
380
469
|
...tuiTokenOpt,
|
|
381
470
|
...containerBrokerOpt,
|
|
382
471
|
}).start()
|
|
383
472
|
|
|
473
|
+
// Tunnel manager starts AFTER the WS server is up so a slow/hanging
|
|
474
|
+
// provider (PR 2's cloudflared first-URL wait) cannot block TUI, reload,
|
|
475
|
+
// or channel adapter availability. External providers resolve URLs
|
|
476
|
+
// synchronously; future managed providers will resolve asynchronously
|
|
477
|
+
// and broadcast URL events when ready.
|
|
478
|
+
await tunnelManager.start()
|
|
479
|
+
|
|
384
480
|
let stopped = false
|
|
385
481
|
const stop = async () => {
|
|
386
482
|
if (stopped) return
|
|
@@ -390,6 +486,8 @@ export async function startAgent({
|
|
|
390
486
|
subagentConsumer.stop()
|
|
391
487
|
server.stop(true)
|
|
392
488
|
void disposeMaterializedSkills(pluginRuntime)
|
|
489
|
+
tunnelBridge.stop()
|
|
490
|
+
await tunnelManager.stop()
|
|
393
491
|
await channelManager.stop()
|
|
394
492
|
}
|
|
395
493
|
|
|
@@ -436,6 +534,15 @@ function buildLocalTuiUrl(port: number, token: string | null): string {
|
|
|
436
534
|
return url.toString()
|
|
437
535
|
}
|
|
438
536
|
|
|
537
|
+
function resolveTunnelUrlForChannel(channelName: string, tunnelManager: TunnelManager): string | null {
|
|
538
|
+
const tunnel = getConfig().tunnels.find((entry) => entry.for.kind === 'channel' && entry.for.name === channelName)
|
|
539
|
+
return tunnel ? tunnelManager.urlFor(tunnel.name) : null
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function isTunnelConfiguredForChannel(channelName: string): boolean {
|
|
543
|
+
return getConfig().tunnels.some((entry) => entry.for.kind === 'channel' && entry.for.name === channelName)
|
|
544
|
+
}
|
|
545
|
+
|
|
439
546
|
async function disposeMaterializedSkills(pluginRuntime: PluginRuntime): Promise<void> {
|
|
440
547
|
const pending = pluginRuntime.drainPendingDisposal()
|
|
441
548
|
const current = pluginRuntime.get().materializedSkills
|
package/src/secrets/index.ts
CHANGED
package/src/secrets/schema.ts
CHANGED
|
@@ -40,6 +40,23 @@ const telegramBotChannelSchema = z.object({
|
|
|
40
40
|
token: secretFieldSchema.optional(),
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
+
const githubPatAuthSchema = z.object({
|
|
44
|
+
type: z.literal('pat'),
|
|
45
|
+
token: secretFieldSchema,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const githubAppAuthSchema = z.object({
|
|
49
|
+
type: z.literal('app'),
|
|
50
|
+
appId: z.number().int().positive(),
|
|
51
|
+
privateKey: secretFieldSchema,
|
|
52
|
+
installationId: z.number().int().positive().optional(),
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const githubChannelSchema = z.object({
|
|
56
|
+
auth: z.discriminatedUnion('type', [githubPatAuthSchema, githubAppAuthSchema]),
|
|
57
|
+
webhookSecret: secretFieldSchema,
|
|
58
|
+
})
|
|
59
|
+
|
|
43
60
|
// Encrypted password envelope produced by src/secrets/encryption.ts. Optional
|
|
44
61
|
// in the schema because legacy v2 accounts (pre-renewal feature) don't have
|
|
45
62
|
// one; the renewal cron treats a missing envelope as "reauth required" and
|
|
@@ -92,6 +109,7 @@ export const channelsSchema = z
|
|
|
92
109
|
.object({
|
|
93
110
|
'slack-bot': slackBotChannelSchema.optional(),
|
|
94
111
|
'discord-bot': discordBotChannelSchema.optional(),
|
|
112
|
+
github: githubChannelSchema.optional(),
|
|
95
113
|
'telegram-bot': telegramBotChannelSchema.optional(),
|
|
96
114
|
kakaotalk: kakaoChannelBlockSchema.optional(),
|
|
97
115
|
})
|
|
@@ -113,6 +131,9 @@ export const secretsFileSchema = z.object({
|
|
|
113
131
|
export type ProviderCredential = z.infer<typeof providerCredentialSchema>
|
|
114
132
|
export type Providers = z.infer<typeof providersSchema>
|
|
115
133
|
export type Channels = z.infer<typeof channelsSchema>
|
|
134
|
+
export type GithubPatAuthBlock = z.infer<typeof githubPatAuthSchema>
|
|
135
|
+
export type GithubAppAuthBlock = z.infer<typeof githubAppAuthSchema>
|
|
136
|
+
export type GithubSecretsBlock = z.infer<typeof githubChannelSchema>
|
|
116
137
|
export type KakaoAccountRecord = z.infer<typeof kakaoAccountRecordSchema>
|
|
117
138
|
export type PendingLoginRecord = z.infer<typeof kakaoPendingLoginRecordSchema>
|
|
118
139
|
export type KakaoChannelBlock = z.infer<typeof kakaoChannelBlockSchema>
|