typeclaw 0.8.0 → 0.9.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 +6 -6
- package/package.json +5 -3
- package/scripts/require-parallel.ts +41 -0
- package/src/agent/index.ts +55 -6
- package/src/agent/live-sessions.ts +34 -0
- package/src/agent/plugin-tools.ts +2 -0
- package/src/agent/session-meta.ts +21 -2
- package/src/agent/subagent-completion-reminder.ts +89 -0
- package/src/agent/subagents.ts +3 -2
- package/src/agent/system-prompt.ts +10 -8
- package/src/bundled-plugins/explorer/explorer.ts +2 -2
- package/src/bundled-plugins/guard/index.ts +14 -1
- package/src/bundled-plugins/guard/policies/managed-config.ts +43 -13
- package/src/bundled-plugins/guard/policies/memory-retrieval-cache-write.ts +37 -0
- package/src/bundled-plugins/guard/policies/memory-topics-delete.ts +67 -0
- package/src/bundled-plugins/guard/policies/memory-topics-write.ts +33 -0
- package/src/bundled-plugins/guard/policies/non-workspace-write.ts +8 -2
- package/src/bundled-plugins/guard/policy.ts +7 -0
- package/src/bundled-plugins/memory/README.md +76 -62
- package/src/bundled-plugins/memory/append-tool.ts +3 -2
- package/src/bundled-plugins/memory/citation-superset.ts +49 -11
- package/src/bundled-plugins/memory/citations.ts +19 -8
- package/src/bundled-plugins/memory/delete-tool.ts +57 -0
- package/src/bundled-plugins/memory/dreaming-state.ts +1 -1
- package/src/bundled-plugins/memory/dreaming.ts +364 -146
- package/src/bundled-plugins/memory/frontmatter.ts +165 -0
- package/src/bundled-plugins/memory/index.ts +236 -16
- package/src/bundled-plugins/memory/injection-plan.ts +15 -0
- package/src/bundled-plugins/memory/load-memory.ts +102 -103
- package/src/bundled-plugins/memory/load-shards.ts +156 -0
- package/src/bundled-plugins/memory/memory-logger.ts +16 -15
- package/src/bundled-plugins/memory/memory-retrieval.ts +105 -0
- package/src/bundled-plugins/memory/migration.ts +282 -1
- package/src/bundled-plugins/memory/paths.ts +42 -0
- package/src/bundled-plugins/memory/search-tool.ts +232 -0
- package/src/bundled-plugins/memory/secret-detector.ts +2 -2
- package/src/bundled-plugins/memory/shard-snapshot.ts +51 -0
- package/src/bundled-plugins/memory/slug.ts +59 -0
- package/src/bundled-plugins/memory/stream-io.ts +110 -1
- package/src/bundled-plugins/memory/strength.ts +3 -3
- package/src/bundled-plugins/memory/topics.ts +70 -16
- package/src/bundled-plugins/security/index.ts +24 -0
- package/src/bundled-plugins/security/permissions.ts +4 -0
- package/src/bundled-plugins/security/policies/cron-promotion.ts +349 -0
- package/src/bundled-plugins/security/policies/git-exfil.ts +2 -0
- package/src/bundled-plugins/security/policies/prompt-injection.ts +3 -0
- package/src/bundled-plugins/security/policies/role-promotion.ts +419 -0
- package/src/bundled-plugins/security/policies/system-prompt-leak.ts +1 -0
- package/src/channels/adapters/kakaotalk-attachment.ts +7 -17
- package/src/channels/adapters/kakaotalk.ts +64 -37
- package/src/channels/adapters/slack-bot-classify.ts +2 -27
- package/src/channels/index.ts +5 -0
- package/src/channels/router.ts +201 -17
- package/src/channels/subagent-completion-bridge.ts +84 -0
- package/src/cli/builtins.ts +1 -0
- package/src/cli/index.ts +1 -0
- package/src/cli/init.ts +122 -14
- package/src/cli/inspect.ts +151 -0
- package/src/cron/consumer.ts +1 -1
- package/src/init/dockerfile.ts +268 -4
- package/src/init/hatching.ts +5 -6
- package/src/init/kakaotalk-auth.ts +6 -47
- package/src/init/validate-api-key.ts +121 -0
- package/src/inspect/index.ts +213 -0
- package/src/inspect/label.ts +50 -0
- package/src/inspect/live.ts +221 -0
- package/src/inspect/render.ts +163 -0
- package/src/inspect/replay.ts +265 -0
- package/src/inspect/session-list.ts +160 -0
- package/src/inspect/types.ts +110 -0
- package/src/plugin/hooks.ts +23 -1
- package/src/plugin/index.ts +2 -0
- package/src/plugin/manager.ts +1 -1
- package/src/plugin/registry.ts +1 -1
- package/src/plugin/types.ts +10 -0
- package/src/run/channel-session-factory.ts +7 -1
- package/src/run/index.ts +87 -21
- package/src/secrets/kakao-renewal.ts +3 -47
- package/src/server/index.ts +241 -60
- package/src/shared/index.ts +3 -0
- package/src/shared/protocol.ts +49 -0
- package/src/skills/typeclaw-channel-kakaotalk/SKILL.md +9 -9
- package/src/skills/typeclaw-claude-code/SKILL.md +57 -39
- package/src/skills/typeclaw-claude-code/references/stop-hook.md +2 -0
- package/src/skills/typeclaw-claude-code/references/tmux-driving.md +102 -16
- package/src/skills/typeclaw-config/SKILL.md +1 -1
- package/src/skills/typeclaw-cron/SKILL.md +1 -1
- package/src/skills/typeclaw-memory/SKILL.md +16 -163
- package/src/skills/typeclaw-permissions/SKILL.md +2 -2
- package/src/skills/typeclaw-plugins/SKILL.md +25 -14
- package/src/test-helpers/wait-for.ts +7 -1
- package/typeclaw.schema.json +7 -0
package/src/run/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SessionManager } from '@mariozechner/pi-coding-agent'
|
|
2
2
|
|
|
3
3
|
import { createSession, createSessionWithDispose } from '@/agent'
|
|
4
|
+
import { LiveSessionRegistry } from '@/agent/live-sessions'
|
|
4
5
|
import { LiveSubagentRegistry } from '@/agent/live-subagents'
|
|
5
6
|
import type { SessionOrigin } from '@/agent/session-origin'
|
|
6
7
|
import {
|
|
@@ -13,7 +14,13 @@ import {
|
|
|
13
14
|
type SubagentShared,
|
|
14
15
|
} from '@/agent/subagents'
|
|
15
16
|
import { resolveCapOptionsFromConfig } from '@/bundled-plugins/tool-result-cap'
|
|
16
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
createChannelManager,
|
|
19
|
+
createChannelsReloadable,
|
|
20
|
+
createSubagentCompletionBridge,
|
|
21
|
+
type ChannelManager,
|
|
22
|
+
type SubagentCompletionBridge,
|
|
23
|
+
} from '@/channels'
|
|
17
24
|
import { createTunnelBridge, type TunnelBridge } from '@/channels/tunnel-bridge'
|
|
18
25
|
import { createConfigReloadable, getConfig, loadConfigSync, loadPluginConfigsSync } from '@/config'
|
|
19
26
|
import {
|
|
@@ -179,6 +186,7 @@ export async function startAgent({
|
|
|
179
186
|
})
|
|
180
187
|
|
|
181
188
|
const liveSubagentRegistry = new LiveSubagentRegistry()
|
|
189
|
+
const liveSessionRegistry = new LiveSessionRegistry()
|
|
182
190
|
|
|
183
191
|
const channelManager = createChannelManagerFor({
|
|
184
192
|
agentDir: cwd,
|
|
@@ -196,6 +204,7 @@ export async function startAgent({
|
|
|
196
204
|
rehydrateCapOptions: resolveCapOptionsFromConfig(pluginConfigsByName['tool-result-cap']),
|
|
197
205
|
permissions: pluginsLoaded.permissions,
|
|
198
206
|
liveSubagentRegistry,
|
|
207
|
+
liveSessionRegistry,
|
|
199
208
|
subagentRegistry: pluginRuntime.get().subagents,
|
|
200
209
|
getCreateSessionForSubagent: () => createSessionForSubagent,
|
|
201
210
|
...containerNameOpt,
|
|
@@ -245,8 +254,14 @@ export async function startAgent({
|
|
|
245
254
|
: {}),
|
|
246
255
|
...runtimeVersionOpt,
|
|
247
256
|
})
|
|
257
|
+
liveSessionRegistry.register({ sessionId, session: created.session })
|
|
258
|
+
const originalDispose = created.dispose
|
|
248
259
|
return {
|
|
249
260
|
...created,
|
|
261
|
+
dispose: async () => {
|
|
262
|
+
liveSessionRegistry.unregister(sessionId)
|
|
263
|
+
await originalDispose()
|
|
264
|
+
},
|
|
250
265
|
hooks: snap.hooks,
|
|
251
266
|
sessionId,
|
|
252
267
|
agentDir: cwd,
|
|
@@ -360,9 +375,13 @@ export async function startAgent({
|
|
|
360
375
|
...containerNameOpt,
|
|
361
376
|
...runtimeVersionOpt,
|
|
362
377
|
})
|
|
378
|
+
liveSessionRegistry.register({ sessionId, session })
|
|
363
379
|
return {
|
|
364
380
|
prompt: (text) => session.prompt(text),
|
|
365
|
-
dispose: () =>
|
|
381
|
+
dispose: () => {
|
|
382
|
+
liveSessionRegistry.unregister(sessionId)
|
|
383
|
+
session.dispose()
|
|
384
|
+
},
|
|
366
385
|
sessionId,
|
|
367
386
|
agentDir: cwd,
|
|
368
387
|
origin: cronOrigin,
|
|
@@ -393,32 +412,77 @@ export async function startAgent({
|
|
|
393
412
|
|
|
394
413
|
const tunnelBridge: TunnelBridge = createTunnelBridge({ stream, channelManager })
|
|
395
414
|
|
|
415
|
+
// Bridge `subagent.completed` broadcasts into the channel router so a
|
|
416
|
+
// backgrounded subagent finishing wakes up its parent channel session
|
|
417
|
+
// with a `<system-reminder>` — symmetric to the TUI bridge in
|
|
418
|
+
// src/server/index.ts. Must be created BEFORE channelManager.start()
|
|
419
|
+
// so an initial broadcast can never race past the subscription gap.
|
|
420
|
+
const subagentCompletionBridge: SubagentCompletionBridge = createSubagentCompletionBridge({
|
|
421
|
+
stream,
|
|
422
|
+
router: channelManager.router,
|
|
423
|
+
})
|
|
424
|
+
|
|
396
425
|
reloadRegistry.register(createChannelsReloadable({ manager: channelManager }))
|
|
397
426
|
await channelManager.start()
|
|
398
427
|
|
|
399
428
|
// Captured separately from setSpawnSubagent so both the plugin context and
|
|
400
429
|
// the plugin-command runner can dispatch through the same path. The setter
|
|
401
430
|
// returns void, so without this local binding we couldn't reuse the fn.
|
|
431
|
+
//
|
|
432
|
+
// In-flight coalescing for direct ctx.spawnSubagent calls mirrors the
|
|
433
|
+
// SubagentConsumer's stream-path gate (subagents.ts:441). Two queued
|
|
434
|
+
// `new-session` messages for the same (name, inFlightKey) drop the second
|
|
435
|
+
// on the consumer side; without the same gate here, two consecutive
|
|
436
|
+
// session.prompt fires (cold-start prompt N immediately followed by prompt
|
|
437
|
+
// N+1 on the same channel session) could both fire memory-retrieval spawns
|
|
438
|
+
// racing to write `memory/.retrieval-cache/<sessionId>.md`. Awaiting the
|
|
439
|
+
// spawn in the hook used to mask this; now that the hook is fire-and-forget,
|
|
440
|
+
// the race is exposed and the gate is mandatory.
|
|
441
|
+
//
|
|
442
|
+
// Same key shape as the consumer: `${name}:${inFlightKey(payload)}` when the
|
|
443
|
+
// subagent declares one, else just `${name}`. Collisions resolve cleanly
|
|
444
|
+
// (logged + return) instead of rejecting, because callers from
|
|
445
|
+
// session.prompt are detached and a colliding spawn is a noop, not an error.
|
|
446
|
+
const directSpawnInFlight = new Set<string>()
|
|
402
447
|
const dispatchSpawnSubagent: CommandSpawnSubagent = async (name, payload, options) => {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
448
|
+
const entry = pluginSubagentByName.get(name)
|
|
449
|
+
const keyFn = entry?.pluginSubagent.inFlightKey
|
|
450
|
+
let coalesceKey = name
|
|
451
|
+
if (keyFn !== undefined) {
|
|
452
|
+
try {
|
|
453
|
+
coalesceKey = `${name}:${keyFn(payload)}`
|
|
454
|
+
} catch {
|
|
455
|
+
coalesceKey = name
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (directSpawnInFlight.has(coalesceKey)) {
|
|
459
|
+
console.warn(`[subagent] ${coalesceKey}: previous direct spawn still in progress, skipping`)
|
|
460
|
+
return
|
|
461
|
+
}
|
|
462
|
+
directSpawnInFlight.add(coalesceKey)
|
|
463
|
+
try {
|
|
464
|
+
// Resolve the spawning session's role from its origin so the subagent
|
|
465
|
+
// inherits it. Callers (hooks like session.idle) pass the parent origin
|
|
466
|
+
// verbatim; we look up the role rather than letting the caller forge it,
|
|
467
|
+
// closing the laundering vector the design doc calls out for cron.
|
|
468
|
+
const spawnedByRole =
|
|
469
|
+
options?.spawnedByOrigin !== undefined
|
|
470
|
+
? pluginsLoaded.permissions.resolveRole(options.spawnedByOrigin)
|
|
471
|
+
: undefined
|
|
472
|
+
await invokeSubagent(name, {
|
|
473
|
+
registry: pluginRuntime.get().subagents,
|
|
474
|
+
createSessionForSubagent,
|
|
475
|
+
agentDir: cwd,
|
|
476
|
+
userPrompt: '',
|
|
477
|
+
payload,
|
|
478
|
+
onProviderError: (message) => console.error(`[subagent] ${name}: LLM call failed: ${message}`),
|
|
479
|
+
...(options?.parentSessionId !== undefined ? { parentSessionId: options.parentSessionId } : {}),
|
|
480
|
+
...(spawnedByRole !== undefined ? { spawnedByRole } : {}),
|
|
481
|
+
...(options?.spawnedByOrigin !== undefined ? { spawnedByOrigin: options.spawnedByOrigin } : {}),
|
|
482
|
+
})
|
|
483
|
+
} finally {
|
|
484
|
+
directSpawnInFlight.delete(coalesceKey)
|
|
485
|
+
}
|
|
422
486
|
}
|
|
423
487
|
pluginsLoaded.setSpawnSubagent(dispatchSpawnSubagent)
|
|
424
488
|
pluginsLoaded.markBooted()
|
|
@@ -477,6 +541,7 @@ export async function startAgent({
|
|
|
477
541
|
tunnelManager,
|
|
478
542
|
liveSubagentRegistry,
|
|
479
543
|
createSessionForSubagent,
|
|
544
|
+
liveSessionRegistry,
|
|
480
545
|
...containerNameOpt,
|
|
481
546
|
...runtimeVersionOpt,
|
|
482
547
|
...tuiTokenOpt,
|
|
@@ -500,6 +565,7 @@ export async function startAgent({
|
|
|
500
565
|
server.stop(true)
|
|
501
566
|
void disposeMaterializedSkills(pluginRuntime)
|
|
502
567
|
tunnelBridge.stop()
|
|
568
|
+
subagentCompletionBridge.stop()
|
|
503
569
|
await tunnelManager.stop()
|
|
504
570
|
await channelManager.stop()
|
|
505
571
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { createRequire } from 'node:module'
|
|
2
1
|
import { join } from 'node:path'
|
|
3
2
|
|
|
4
|
-
import type
|
|
3
|
+
import { attemptLogin as upstreamAttemptLogin, type KakaoDeviceType } from 'agent-messenger/kakaotalk'
|
|
5
4
|
|
|
6
5
|
import { decrypt, EncryptionError } from './encryption'
|
|
7
6
|
import { SecretsKakaoCredentialStore } from './kakao-store'
|
|
@@ -9,21 +8,6 @@ import { type KeyStore, KeyStoreError } from './keys'
|
|
|
9
8
|
import { type KakaoChannelBlock } from './schema'
|
|
10
9
|
import { SecretsBackend } from './storage'
|
|
11
10
|
|
|
12
|
-
// Mirrors KakaoLoginResult from agent-messenger/kakaotalk's types.d.ts. The
|
|
13
|
-
// upstream interface is not re-exported from the package root, so we declare
|
|
14
|
-
// the structural shape locally. If a future version adds new fields, this
|
|
15
|
-
// stays forward-compatible because we only read the ones declared here.
|
|
16
|
-
export type KakaoLoginResult = {
|
|
17
|
-
authenticated: boolean
|
|
18
|
-
next_action?: string
|
|
19
|
-
message?: string
|
|
20
|
-
warning?: string
|
|
21
|
-
account_id?: string
|
|
22
|
-
device_type?: KakaoDeviceType
|
|
23
|
-
user_id?: string
|
|
24
|
-
error?: string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
11
|
export const RENEWAL_THRESHOLD_MS = 5 * 24 * 60 * 60 * 1000
|
|
28
12
|
|
|
29
13
|
// Hard ~7-day TTL on KakaoTalk sub-device tokens means renewal must happen
|
|
@@ -50,21 +34,7 @@ export type RenewalAttempt =
|
|
|
50
34
|
| { kind: 'reauth_required'; account_id: string; reason: string; message: string }
|
|
51
35
|
| { kind: 'transient_failure'; account_id: string; reason: string }
|
|
52
36
|
|
|
53
|
-
export type AttemptLoginFn =
|
|
54
|
-
email: string,
|
|
55
|
-
password: string,
|
|
56
|
-
deviceUuid: string,
|
|
57
|
-
deviceType: KakaoDeviceType,
|
|
58
|
-
forced: boolean,
|
|
59
|
-
) => Promise<KakaoLoginResult & { credentials?: LoginCredentials }>
|
|
60
|
-
|
|
61
|
-
export type LoginCredentials = {
|
|
62
|
-
access_token: string
|
|
63
|
-
refresh_token: string
|
|
64
|
-
user_id: string
|
|
65
|
-
device_uuid: string
|
|
66
|
-
device_type: KakaoDeviceType
|
|
67
|
-
}
|
|
37
|
+
export type AttemptLoginFn = typeof upstreamAttemptLogin
|
|
68
38
|
|
|
69
39
|
export type RenewalContext = {
|
|
70
40
|
containerName: string
|
|
@@ -151,7 +121,7 @@ export async function renewCurrentAccount(
|
|
|
151
121
|
}
|
|
152
122
|
}
|
|
153
123
|
|
|
154
|
-
const attemptLogin = ctx.attemptLogin ??
|
|
124
|
+
const attemptLogin = ctx.attemptLogin ?? upstreamAttemptLogin
|
|
155
125
|
const result = await attemptLogin(
|
|
156
126
|
decision.account.email,
|
|
157
127
|
decision.password,
|
|
@@ -232,17 +202,3 @@ function classifyDecryptFailure(err: unknown, accountId: string): RenewalDecisio
|
|
|
232
202
|
message: `Could not decrypt stored KakaoTalk password (${err instanceof Error ? err.message : String(err)}).`,
|
|
233
203
|
}
|
|
234
204
|
}
|
|
235
|
-
|
|
236
|
-
async function resolveAttemptLogin(): Promise<AttemptLoginFn> {
|
|
237
|
-
// agent-messenger does not export `attemptLogin` from its public exports
|
|
238
|
-
// map. Resolve the package's installed location and import the auth
|
|
239
|
-
// implementation file directly — same pattern as runKakaotalkBootstrap's
|
|
240
|
-
// loginFlow resolution in src/init/kakaotalk-auth.ts.
|
|
241
|
-
const require = createRequire(import.meta.url)
|
|
242
|
-
const pkgJson = require.resolve('agent-messenger/package.json')
|
|
243
|
-
const pkgDir = pkgJson.replace(/\/package\.json$/, '')
|
|
244
|
-
const mod = (await import(`${pkgDir}/dist/src/platforms/kakaotalk/auth/kakao-login.js`)) as {
|
|
245
|
-
attemptLogin: AttemptLoginFn
|
|
246
|
-
}
|
|
247
|
-
return mod.attemptLogin
|
|
248
|
-
}
|