typeclaw 0.37.4 → 0.37.6
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/package.json +1 -1
- package/src/agent/doctor.ts +6 -1
- package/src/agent/plugin-tools.ts +23 -1
- package/src/agent/subagents.ts +146 -14
- package/src/agent/todo/scope.ts +4 -2
- package/src/agent/tools/channel-reply.ts +7 -9
- package/src/bundled-plugins/doc-render/index.ts +10 -0
- package/src/bundled-plugins/doc-render/skills/typeclaw-render-pdf/SKILL.md +171 -165
- package/src/bundled-plugins/doc-render/templates/lib.typ +339 -0
- package/src/bundled-plugins/github-cli-auth/gh-command.ts +95 -11
- package/src/bundled-plugins/github-cli-auth/git-command.ts +11 -0
- package/src/bundled-plugins/github-cli-auth/index.ts +68 -7
- package/src/bundled-plugins/memory/index.ts +9 -6
- package/src/bundled-plugins/memory/load-memory.ts +16 -2
- package/src/bundled-plugins/memory/slug.ts +19 -0
- package/src/bundled-plugins/security/policies/private-surface-read.ts +4 -1
- 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 +17 -0
- package/src/channels/manager.ts +77 -1
- package/src/channels/router.ts +181 -12
- package/src/cli/compose.ts +11 -2
- package/src/cli/dreams.ts +2 -2
- package/src/cli/inspect.ts +2 -2
- package/src/cli/logs.ts +2 -2
- package/src/cli/mount.ts +5 -5
- package/src/cli/require-agent-dir.ts +31 -0
- package/src/cli/restart.ts +2 -1
- package/src/cli/shell.ts +2 -2
- package/src/cli/start.ts +2 -1
- package/src/cli/stop.ts +2 -2
- package/src/cli/tui.ts +20 -6
- package/src/cli/ui.ts +13 -0
- package/src/compose/restart.ts +1 -1
- package/src/compose/start.ts +4 -2
- package/src/config/config.ts +200 -9
- package/src/container/shared.ts +18 -0
- package/src/container/start.ts +1 -1
- package/src/cron/consumer.ts +3 -3
- 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/init/dockerfile.ts +11 -8
- package/src/init/kakaotalk-auth.ts +2 -2
- package/src/init/packagejson.ts +2 -2
- package/src/plugin/loader.ts +7 -4
- package/src/sandbox/session-tmp.ts +6 -1
- package/src/secrets/export-claude-credentials-file.ts +2 -2
package/src/hostd/paths.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
1
2
|
import { chmod, mkdir } from 'node:fs/promises'
|
|
2
|
-
import { homedir } from 'node:os'
|
|
3
|
-
import { join } from 'node:path'
|
|
3
|
+
import { homedir, userInfo } from 'node:os'
|
|
4
|
+
import { join, resolve } from 'node:path'
|
|
5
|
+
|
|
6
|
+
import { isWindows } from '@/shared'
|
|
4
7
|
|
|
5
8
|
// Fixed in-container path where the host daemon's run dir is bind-mounted.
|
|
6
9
|
// The agent uses this to reach the host daemon (e.g. for the `restart` tool).
|
|
@@ -33,9 +36,26 @@ export function logDir(): string {
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
export function socketPath(): string {
|
|
39
|
+
if (isWindows()) return windowsPipePath()
|
|
36
40
|
return join(runDir(), SOCKET_FILE)
|
|
37
41
|
}
|
|
38
42
|
|
|
43
|
+
function windowsPipePath(): string {
|
|
44
|
+
const uid =
|
|
45
|
+
typeof process.getuid === 'function'
|
|
46
|
+
? `uid:${process.getuid()}`
|
|
47
|
+
: `user:${process.env.USERDOMAIN ?? ''}\\${userInfo().username}`
|
|
48
|
+
// Locale-invariant lowercasing: toLocaleLowerCase under e.g. tr-TR would map
|
|
49
|
+
// 'I' to a dotless 'ı', hashing the same path differently per process locale.
|
|
50
|
+
const scopedHome = resolve(homeRoot()).toLowerCase()
|
|
51
|
+
const hash = createHash('sha256').update(`${uid}\0${scopedHome}`).digest('hex').slice(0, 32)
|
|
52
|
+
|
|
53
|
+
// Node's net named-pipe API has no portable ACL hook. TypeClaw accepts that
|
|
54
|
+
// under the single-tenant dev-box model; the per-user/per-home pipe name keeps
|
|
55
|
+
// the pipe scoped, while the separate HTTP leg remains restart/secrets-only.
|
|
56
|
+
return `\\\\.\\pipe\\typeclaw-hostd-${hash}`
|
|
57
|
+
}
|
|
58
|
+
|
|
39
59
|
export function pidfilePath(): string {
|
|
40
60
|
return join(runDir(), 'hostd.pid')
|
|
41
61
|
}
|
package/src/hostd/spawn.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { open, readFile, unlink, writeFile } from 'node:fs/promises'
|
|
3
3
|
|
|
4
|
+
import { isWindows } from '@/shared'
|
|
5
|
+
|
|
4
6
|
import { isDaemonReachable, send } from './client'
|
|
5
7
|
import { ensureDirs, lockfilePath, logfilePath, pidfilePath, socketPath } from './paths'
|
|
6
8
|
import type { HttpInfoResult, VersionResult } from './protocol'
|
|
@@ -75,6 +77,11 @@ async function requestShutdownAndWait(): Promise<boolean> {
|
|
|
75
77
|
if (!reply.ok) return false
|
|
76
78
|
const deadline = Date.now() + SHUTDOWN_TIMEOUT_MS
|
|
77
79
|
while (Date.now() < deadline) {
|
|
80
|
+
if (isWindows()) {
|
|
81
|
+
if (!(await isDaemonReachable(POLL_INTERVAL_MS))) return true
|
|
82
|
+
await sleep(POLL_INTERVAL_MS)
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
78
85
|
if (!existsSync(socketPath())) return true
|
|
79
86
|
await sleep(POLL_INTERVAL_MS)
|
|
80
87
|
}
|
package/src/init/dockerfile.ts
CHANGED
|
@@ -358,9 +358,9 @@ set -eu
|
|
|
358
358
|
# The persist root lives under /agent/.typeclaw/home/ (bind-mounted
|
|
359
359
|
# from the agent folder via the -v <cwd>:/agent flag in start.ts).
|
|
360
360
|
# Namespacing under .typeclaw/ keeps the agent's top-level layout clean and reserves
|
|
361
|
-
# a system-owned subtree we can extend later (e.g. ~/.gemini
|
|
362
|
-
#
|
|
363
|
-
#
|
|
361
|
+
# a system-owned subtree we can extend later (e.g. ~/.gemini/) without
|
|
362
|
+
# colliding with user files. The directory is gitignored by buildGitignore()
|
|
363
|
+
# so credentials never enter history.
|
|
364
364
|
#
|
|
365
365
|
# Three invariants this function enforces:
|
|
366
366
|
#
|
|
@@ -372,11 +372,11 @@ set -eu
|
|
|
372
372
|
# if a previous container life happened to write a real ~/.codex/
|
|
373
373
|
# dir before this code shipped.
|
|
374
374
|
#
|
|
375
|
-
# 2. We symlink
|
|
376
|
-
#
|
|
377
|
-
#
|
|
378
|
-
#
|
|
379
|
-
#
|
|
375
|
+
# 2. We symlink credential FILES for tools whose config dirs are mostly
|
|
376
|
+
# scratch/history (Codex, Claude). We do not redirect global config
|
|
377
|
+
# locations such as XDG_CONFIG_HOME or ~/.config here because tools like
|
|
378
|
+
# git also read config from those paths; first-party bundles that need
|
|
379
|
+
# persistence should set their own app-specific env vars instead.
|
|
380
380
|
#
|
|
381
381
|
# 3. We mkdir -p the target's parent on every boot. /agent is bind-
|
|
382
382
|
# mounted, so the host-side path may exist or not depending on
|
|
@@ -1264,6 +1264,9 @@ ${fromAndHeavyLayers}
|
|
|
1264
1264
|
|
|
1265
1265
|
ENV NODE_ENV=production
|
|
1266
1266
|
|
|
1267
|
+
# Persist first-party GWS config without changing global XDG/git config lookup.
|
|
1268
|
+
ENV GWS_CONFIG_HOME=/agent/workspace/.config/gws
|
|
1269
|
+
|
|
1267
1270
|
# Keep agent-messenger's fallback config dir inside workspace/ for any future
|
|
1268
1271
|
# SDK fallback paths. TypeClaw's KakaoTalk adapter does not write there:
|
|
1269
1272
|
# credentials live in secrets.json#channels.kakaotalk and container writes go
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join, resolve } from 'node:path'
|
|
1
|
+
import { join, posix, resolve } from 'node:path'
|
|
2
2
|
|
|
3
3
|
import { loginFlow as upstreamLoginFlow } from 'agent-messenger/kakaotalk'
|
|
4
4
|
|
|
@@ -34,7 +34,7 @@ export type LoginFlowOptions = Parameters<LoginFlowFn>[0]
|
|
|
34
34
|
export type LoginFlowResult = Awaited<ReturnType<LoginFlowFn>>
|
|
35
35
|
|
|
36
36
|
export function kakaotalkConfigDir(agentDir: string): string {
|
|
37
|
-
return join(agentDir, 'workspace', '.agent-messenger')
|
|
37
|
+
return posix.join(agentDir, 'workspace', '.agent-messenger')
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export function kakaotalkSecretsPath(agentDir: string): string {
|
package/src/init/packagejson.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
3
|
-
import { join } from 'node:path'
|
|
3
|
+
import { join, posix } from 'node:path'
|
|
4
4
|
|
|
5
5
|
import { GITKEEP_FILE, PACKAGES_DIR } from './paths'
|
|
6
6
|
|
|
@@ -33,7 +33,7 @@ export async function refreshPackageJson(cwd: string): Promise<PackageJsonRefres
|
|
|
33
33
|
if (updated) changed.push(PACKAGE_FILE)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const gitkeepRel = join(PACKAGES_DIR, GITKEEP_FILE)
|
|
36
|
+
const gitkeepRel = posix.join(PACKAGES_DIR, GITKEEP_FILE)
|
|
37
37
|
const gitkeepPath = join(cwd, gitkeepRel)
|
|
38
38
|
if (!existsSync(gitkeepPath)) {
|
|
39
39
|
await mkdir(join(cwd, PACKAGES_DIR), { recursive: true })
|
package/src/plugin/loader.ts
CHANGED
|
@@ -61,8 +61,7 @@ async function loadLocal(entry: string, agentDir: string): Promise<ResolvedPlugi
|
|
|
61
61
|
if (!existsSync(resolved)) {
|
|
62
62
|
throw new PluginNotFoundError(entry, `plugin path does not exist: ${entry} (resolved to ${resolved})`)
|
|
63
63
|
}
|
|
64
|
-
const
|
|
65
|
-
const mod = (await import(url)) as { default?: unknown }
|
|
64
|
+
const mod = (await import(toModuleSpecifier(resolved))) as { default?: unknown }
|
|
66
65
|
const defined = expectDefined(mod, entry)
|
|
67
66
|
const name = basename(resolved).replace(/\.(ts|tsx|js|mjs|cjs)$/i, '')
|
|
68
67
|
return { name, version: undefined, source: entry, defined }
|
|
@@ -108,10 +107,10 @@ async function loadNpm(entry: string, agentDir: string): Promise<ResolvedPlugin>
|
|
|
108
107
|
// located on disk; the else branch lets Bun's resolver read `exports` maps.
|
|
109
108
|
let importTarget: string
|
|
110
109
|
if (entryPath !== null) {
|
|
111
|
-
importTarget =
|
|
110
|
+
importTarget = toModuleSpecifier(entryPath)
|
|
112
111
|
} else {
|
|
113
112
|
try {
|
|
114
|
-
importTarget = Bun.resolveSync(packageName, agentDir)
|
|
113
|
+
importTarget = toModuleSpecifier(Bun.resolveSync(packageName, agentDir))
|
|
115
114
|
} catch (err) {
|
|
116
115
|
throw new PluginNotFoundError(entry, `cannot resolve plugin "${entry}": ${describeError(err)}`, { cause: err })
|
|
117
116
|
}
|
|
@@ -151,6 +150,10 @@ function describeError(err: unknown): string {
|
|
|
151
150
|
return err instanceof Error ? err.message : String(err)
|
|
152
151
|
}
|
|
153
152
|
|
|
153
|
+
function toModuleSpecifier(target: string): string {
|
|
154
|
+
return isAbsolute(target) ? pathToFileURL(target).href : target
|
|
155
|
+
}
|
|
156
|
+
|
|
154
157
|
function findPackageJson(entry: string, agentDir: string): string | null {
|
|
155
158
|
const PACKAGE_JSON = 'package.json'
|
|
156
159
|
let cur = agentDir
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { mkdir } from 'node:fs/promises'
|
|
2
|
-
import {
|
|
2
|
+
import { posix } from 'node:path'
|
|
3
|
+
|
|
4
|
+
// Container-only code over the POSIX `/tmp`; pinned to `path.posix` so the test
|
|
5
|
+
// suite produces the same backing paths on a win32 runner (default `node:path`
|
|
6
|
+
// would yield `\tmp\…` and diverge from the Linux runtime).
|
|
7
|
+
const { isAbsolute, join, relative, resolve } = posix
|
|
3
8
|
|
|
4
9
|
// Per-session scratch lives on the REAL container /tmp, namespaced by session id.
|
|
5
10
|
// It sits OUTSIDE the agent folder on purpose: the agent folder's `sessions/` is
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
writeFileSync,
|
|
10
10
|
} from 'node:fs'
|
|
11
11
|
import { homedir } from 'node:os'
|
|
12
|
-
import { dirname, isAbsolute, join, resolve } from 'node:path'
|
|
12
|
+
import { dirname, isAbsolute, join, posix, resolve } from 'node:path'
|
|
13
13
|
|
|
14
14
|
import { decodeClaudeAccessTokenExpiryMs, emitClaudeCredentialsJson } from './claude-credentials-json'
|
|
15
15
|
import type { ProviderCredential, Providers } from './schema'
|
|
@@ -19,7 +19,7 @@ const FILE_MODE = 0o600
|
|
|
19
19
|
const DIR_MODE = 0o700
|
|
20
20
|
export const CLAUDE_CREDENTIALS_FILE_NAME = '.credentials.json'
|
|
21
21
|
export const CLAUDE_DEFAULT_CONFIG_DIR_NAME = '.claude'
|
|
22
|
-
export const CLAUDE_CREDENTIALS_RELATIVE_PATH = join(CLAUDE_DEFAULT_CONFIG_DIR_NAME, CLAUDE_CREDENTIALS_FILE_NAME)
|
|
22
|
+
export const CLAUDE_CREDENTIALS_RELATIVE_PATH = posix.join(CLAUDE_DEFAULT_CONFIG_DIR_NAME, CLAUDE_CREDENTIALS_FILE_NAME)
|
|
23
23
|
|
|
24
24
|
export type ExportClaudeCredentialsFileResult =
|
|
25
25
|
| { action: 'skipped'; reason: SkipReason }
|