typeclaw 0.36.4 → 0.36.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typeclaw",
3
- "version": "0.36.4",
3
+ "version": "0.36.5",
4
4
  "homepage": "https://github.com/typeclaw/typeclaw#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/typeclaw/typeclaw/issues"
@@ -48,7 +48,7 @@
48
48
  "@mariozechner/pi-tui": "^0.67.3",
49
49
  "@modelcontextprotocol/sdk": "^1.29.0",
50
50
  "@mozilla/readability": "^0.6.0",
51
- "agent-messenger": "2.19.4",
51
+ "agent-messenger": "2.19.5",
52
52
  "cheerio": "^1.2.0",
53
53
  "citty": "^0.2.2",
54
54
  "cron-parser": "^5.5.0",
@@ -25,6 +25,26 @@ const knownModelRefs = listKnownModelRefs() as [KnownModelRef, ...KnownModelRef[
25
25
  // T9 keypad: T=8, Y=9, P=7, E=3
26
26
  const DEFAULT_PORT = 8973
27
27
 
28
+ export const GWS_MULTI_ACCOUNT_PLUGIN_PACKAGE = 'typeclaw-gws-multi-account'
29
+ export const GWS_MULTI_ACCOUNT_PLUGIN_VERSION = '^0.3.4'
30
+ export const DEFAULT_PLUGINS = [`${GWS_MULTI_ACCOUNT_PLUGIN_PACKAGE}@${GWS_MULTI_ACCOUNT_PLUGIN_VERSION}`] as const
31
+
32
+ export function withDefaultPlugins(plugins: readonly string[]): string[] {
33
+ const configuredNames = new Set(plugins.map(pluginPackageName))
34
+ const defaults = DEFAULT_PLUGINS.filter((entry) => !configuredNames.has(pluginPackageName(entry)))
35
+ return [...defaults, ...plugins]
36
+ }
37
+
38
+ function pluginPackageName(entry: string): string {
39
+ if (entry.startsWith('@')) {
40
+ const slash = entry.indexOf('/')
41
+ const at = slash === -1 ? -1 : entry.indexOf('@', slash + 1)
42
+ return at === -1 ? entry : entry.slice(0, at)
43
+ }
44
+ const at = entry.indexOf('@')
45
+ return at === -1 ? entry : entry.slice(0, at)
46
+ }
47
+
28
48
  // Mount names land on disk as `mounts/<name>` inside the agent folder, so they
29
49
  // share a namespace with regular filenames. Restricting to lowercase
30
50
  // alphanumerics + `-`/`_` keeps them shell-safe and avoids accidental shadowing
@@ -2,6 +2,7 @@ export {
2
2
  buildConfigMigrationCommitMessage,
3
3
  config,
4
4
  configSchema,
5
+ DEFAULT_PLUGINS,
5
6
  dockerSchema,
6
7
  dockerfileSchema,
7
8
  expandMountPath,
@@ -9,6 +10,8 @@ export {
9
10
  getConfig,
10
11
  gitSchema,
11
12
  gitignoreSchema,
13
+ GWS_MULTI_ACCOUNT_PLUGIN_PACKAGE,
14
+ GWS_MULTI_ACCOUNT_PLUGIN_VERSION,
12
15
  loadConfigSync,
13
16
  loadConfigSyncOrDefaults,
14
17
  loadPluginConfigsSync,
@@ -22,6 +25,7 @@ export {
22
25
  resolveProfile,
23
26
  validateConfig,
24
27
  validateMount,
28
+ withDefaultPlugins,
25
29
  type Config,
26
30
  type ConfigChange,
27
31
  type ConfigReloadDiff,
@@ -3,7 +3,7 @@ import { existsSync } from 'node:fs'
3
3
  import { readFile, writeFile } from 'node:fs/promises'
4
4
  import { isAbsolute, join, resolve } from 'node:path'
5
5
 
6
- import { expandMountPath, loadConfigSync, type Config } from '@/config'
6
+ import { expandMountPath, loadConfigSync, withDefaultPlugins, type Config } from '@/config'
7
7
  import { commitGitignoreWithUntracks, untrackTrulyIgnoredFiles } from '@/git/reconcile-ignored'
8
8
  import { commitSystemFile as commitSystemFileShared } from '@/git/system-commit'
9
9
  import { send as sendToDaemon } from '@/hostd/client'
@@ -263,7 +263,7 @@ export async function start({
263
263
  // field is trustworthy by construction.
264
264
  const pluginReconcile = await reconcilePluginDeps({
265
265
  cwd,
266
- plugins: (await loadTypeclawConfig(cwd)).plugins,
266
+ plugins: withDefaultPlugins((await loadTypeclawConfig(cwd)).plugins),
267
267
  }).catch((error: unknown) => ({ error: error instanceof Error ? error.message : String(error) }) as const)
268
268
  if ('error' in pluginReconcile) {
269
269
  return { ok: false, reason: `plugin dependency reconcile failed: ${pluginReconcile.error}` }
package/src/init/index.ts CHANGED
@@ -3,7 +3,14 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises'
3
3
  import { basename, dirname, join, relative, resolve } from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
5
 
6
- import { config, configSchema, migrateLegacyConfigShape, type Config } from '@/config'
6
+ import {
7
+ config,
8
+ configSchema,
9
+ GWS_MULTI_ACCOUNT_PLUGIN_PACKAGE,
10
+ GWS_MULTI_ACCOUNT_PLUGIN_VERSION,
11
+ migrateLegacyConfigShape,
12
+ type Config,
13
+ } from '@/config'
7
14
  import {
8
15
  DEFAULT_MODEL_REF,
9
16
  KNOWN_PROVIDERS,
@@ -577,7 +584,6 @@ export async function scaffold(root: string, options: ScaffoldOptions = {}): Pro
577
584
  // to function. The Dockerfile pre-downloads Chromium too, so the agent
578
585
  // can drive a browser without any first-run setup.
579
586
  const AGENT_BROWSER_VERSION = '^0.26.0'
580
-
581
587
  function buildPackageJson(root: string, name: string): Record<string, unknown> {
582
588
  return {
583
589
  name,
@@ -587,6 +593,12 @@ function buildPackageJson(root: string, name: string): Record<string, unknown> {
587
593
  dependencies: {
588
594
  typeclaw: resolveTypeclawSpec(root),
589
595
  'agent-browser': AGENT_BROWSER_VERSION,
596
+ [GWS_MULTI_ACCOUNT_PLUGIN_PACKAGE]: GWS_MULTI_ACCOUNT_PLUGIN_VERSION,
597
+ },
598
+ typeclaw: {
599
+ managedPlugins: {
600
+ [GWS_MULTI_ACCOUNT_PLUGIN_PACKAGE]: GWS_MULTI_ACCOUNT_PLUGIN_VERSION,
601
+ },
590
602
  },
591
603
  }
592
604
  }
@@ -44,6 +44,8 @@ export type LineLoginClient = {
44
44
  }): Promise<LineLoginResult>
45
45
  }
46
46
 
47
+ let lineTokenInfoSuppressionQueue: Promise<void> = Promise.resolve()
48
+
47
49
  export function lineSecretsPath(agentDir: string): string {
48
50
  return join(agentDir, 'secrets.json')
49
51
  }
@@ -58,19 +60,20 @@ export async function runLineBootstrap(input: LineLoginInput): Promise<LineBoots
58
60
  // ~/.config/agent-messenger to keep in sync.
59
61
  const client = input.client ?? buildLineClient(store)
60
62
 
61
- const result =
63
+ const result = await suppressLineTokenInfoDump(() =>
62
64
  input.method === 'qr'
63
- ? await client.loginWithQR({
65
+ ? client.loginWithQR({
64
66
  onQRUrl: async (url) => {
65
67
  await input.callbacks.onQRUrl?.(url)
66
68
  },
67
69
  onPincode: input.callbacks.onPincode,
68
70
  })
69
- : await client.loginWithEmail({
71
+ : client.loginWithEmail({
70
72
  email: input.email,
71
73
  password: input.password,
72
74
  onPincode: input.callbacks.onPincode,
73
- })
75
+ }),
76
+ )
74
77
 
75
78
  if (!result.authenticated || result.account_id === undefined) {
76
79
  const reason = result.message ?? result.error ?? 'LINE login did not authenticate'
@@ -101,3 +104,45 @@ function buildLineClient(store: SecretsLineCredentialStore): LineLoginClient {
101
104
  const credManager = store as unknown as LineCredentialManager
102
105
  return new RealLineClient(credManager) as unknown as LineLoginClient
103
106
  }
107
+
108
+ async function suppressLineTokenInfoDump<T>(fn: () => Promise<T>): Promise<T> {
109
+ const previous = lineTokenInfoSuppressionQueue
110
+ let release: () => void = () => {}
111
+ lineTokenInfoSuppressionQueue = new Promise((resolve) => {
112
+ release = resolve
113
+ })
114
+ await previous
115
+
116
+ const originalLog = console.log
117
+ console.log = (...args: unknown[]) => {
118
+ if (isLineTokenInfoDump(args)) return
119
+ originalLog(...args)
120
+ }
121
+ try {
122
+ return await fn()
123
+ } finally {
124
+ console.log = originalLog
125
+ release()
126
+ }
127
+ }
128
+
129
+ function isLineTokenInfoDump(args: unknown[]): boolean {
130
+ if (args.length !== 1) return false
131
+ const value = args[0]
132
+ if (value === null || typeof value !== 'object') return false
133
+
134
+ const record = value as Record<string, unknown>
135
+ return (
136
+ looksLikeJwt(record['1']) &&
137
+ looksLikeJwt(record['2']) &&
138
+ typeof record['3'] === 'number' &&
139
+ typeof record['4'] === 'object' &&
140
+ typeof record['5'] === 'string' &&
141
+ typeof record['6'] === 'number'
142
+ )
143
+ }
144
+
145
+ function looksLikeJwt(value: unknown): boolean {
146
+ if (typeof value !== 'string') return false
147
+ return /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(value)
148
+ }
package/src/run/index.ts CHANGED
@@ -28,7 +28,14 @@ import {
28
28
  type SubagentCompletionBridge,
29
29
  } from '@/channels'
30
30
  import { createTunnelBridge, type TunnelBridge } from '@/channels/tunnel-bridge'
31
- import { createConfigReloadable, getConfig, loadConfigSync, loadPluginConfigsSync, reloadConfig } from '@/config'
31
+ import {
32
+ createConfigReloadable,
33
+ getConfig,
34
+ loadConfigSync,
35
+ loadPluginConfigsSync,
36
+ reloadConfig,
37
+ withDefaultPlugins,
38
+ } from '@/config'
32
39
  import {
33
40
  type CountStore,
34
41
  type CronConsumer,
@@ -162,7 +169,7 @@ export async function startAgent({
162
169
  }
163
170
  const mcpManagerOpt = mcpManager !== null ? { mcpManager } : {}
164
171
  const pluginsLoaded = await loadPlugins({
165
- entries: cwdConfig.plugins,
172
+ entries: withDefaultPlugins(cwdConfig.plugins),
166
173
  agentDir: cwd,
167
174
  configsByName: pluginConfigsByName,
168
175
  bundled: BUNDLED_PLUGINS,