typeclaw 0.36.8 → 0.37.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.
Files changed (111) hide show
  1. package/README.md +2 -2
  2. package/package.json +3 -2
  3. package/src/agent/index.ts +31 -11
  4. package/src/agent/live-sessions.ts +12 -0
  5. package/src/agent/model-fallback.ts +17 -15
  6. package/src/agent/model-overrides.ts +2 -2
  7. package/src/agent/session-meta.ts +10 -0
  8. package/src/agent/subagents.ts +11 -2
  9. package/src/agent/system-prompt.ts +9 -3
  10. package/src/agent/todo/continuation-policy.ts +6 -3
  11. package/src/agent/todo/continuation-wiring.ts +4 -2
  12. package/src/agent/todo/continuation.ts +3 -3
  13. package/src/agent/tools/todo/index.ts +27 -4
  14. package/src/bundled-plugins/agent-browser/index.ts +33 -108
  15. package/src/bundled-plugins/agent-browser/shim.ts +3 -94
  16. package/src/bundled-plugins/agent-browser/skills/agent-browser/SKILL.md +8 -33
  17. package/src/bundled-plugins/doc-render/skills/typeclaw-render-pdf/SKILL.md +2 -2
  18. package/src/bundled-plugins/guard/policies/memory-retrieval-cache-write.ts +7 -1
  19. package/src/bundled-plugins/memory/README.md +80 -23
  20. package/src/bundled-plugins/memory/append-tool.ts +74 -53
  21. package/src/bundled-plugins/memory/citation-superset.ts +4 -0
  22. package/src/bundled-plugins/memory/citations.ts +54 -0
  23. package/src/bundled-plugins/memory/dreaming-metrics.ts +30 -0
  24. package/src/bundled-plugins/memory/dreaming.ts +444 -21
  25. package/src/bundled-plugins/memory/index.ts +544 -400
  26. package/src/bundled-plugins/memory/load-memory.ts +87 -10
  27. package/src/bundled-plugins/memory/load-shards.ts +48 -22
  28. package/src/bundled-plugins/memory/memory-logger.ts +95 -106
  29. package/src/bundled-plugins/memory/memory-retrieval.ts +3 -3
  30. package/src/bundled-plugins/memory/parent-link.ts +33 -0
  31. package/src/bundled-plugins/memory/paths.ts +12 -0
  32. package/src/bundled-plugins/memory/references/frontmatter.ts +197 -0
  33. package/src/bundled-plugins/memory/references/load-references.ts +212 -0
  34. package/src/bundled-plugins/memory/references/store-reference-tool.ts +59 -0
  35. package/src/bundled-plugins/memory/search-tool.ts +282 -45
  36. package/src/bundled-plugins/memory/stream-events.ts +1 -0
  37. package/src/bundled-plugins/memory/stream-io.ts +28 -3
  38. package/src/bundled-plugins/memory/turn-dedup.ts +40 -0
  39. package/src/bundled-plugins/memory/vector/cache-write.ts +19 -0
  40. package/src/bundled-plugins/memory/vector/config.ts +28 -0
  41. package/src/bundled-plugins/memory/vector/doctor.ts +124 -0
  42. package/src/bundled-plugins/memory/vector/embedder.ts +246 -0
  43. package/src/bundled-plugins/memory/vector/hybrid.ts +439 -0
  44. package/src/bundled-plugins/memory/vector/index-on-write.ts +34 -0
  45. package/src/bundled-plugins/memory/vector/inspect.ts +111 -0
  46. package/src/bundled-plugins/memory/vector/passages.ts +125 -0
  47. package/src/bundled-plugins/memory/vector/reference-index-on-write.ts +50 -0
  48. package/src/bundled-plugins/memory/vector/relevance-gate.ts +93 -0
  49. package/src/bundled-plugins/memory/vector/startup.ts +71 -0
  50. package/src/bundled-plugins/memory/vector/store.ts +203 -0
  51. package/src/bundled-plugins/memory/vector/truncation.ts +124 -0
  52. package/src/bundled-plugins/security/policies/outbound-secret-scan.ts +2 -0
  53. package/src/channels/router.ts +239 -40
  54. package/src/cli/incomplete-init.ts +57 -0
  55. package/src/cli/init.ts +143 -12
  56. package/src/cli/inspect.ts +11 -5
  57. package/src/cli/model.ts +112 -34
  58. package/src/cli/restart.ts +24 -0
  59. package/src/cli/start.ts +24 -0
  60. package/src/cli/tunnel.ts +53 -8
  61. package/src/config/config.ts +110 -19
  62. package/src/config/index.ts +5 -1
  63. package/src/config/models-mutation.ts +29 -11
  64. package/src/config/providers-mutation.ts +2 -2
  65. package/src/config/providers.ts +146 -12
  66. package/src/container/shared.ts +9 -0
  67. package/src/container/start.ts +87 -4
  68. package/src/cron/consumer.ts +13 -7
  69. package/src/hostd/models.ts +64 -0
  70. package/src/hostd/paths.ts +6 -0
  71. package/src/hostd/portbroker-manager.ts +2 -2
  72. package/src/init/checkpoint.ts +201 -0
  73. package/src/init/dockerfile.ts +121 -34
  74. package/src/init/gitignore.ts +7 -7
  75. package/src/init/index.ts +41 -9
  76. package/src/init/models-dev.ts +96 -21
  77. package/src/init/oauth-login.ts +3 -3
  78. package/src/init/progress.ts +29 -0
  79. package/src/init/validate-api-key.ts +4 -0
  80. package/src/inspect/index.ts +13 -6
  81. package/src/inspect/item-list.ts +11 -2
  82. package/src/inspect/live-list.ts +65 -0
  83. package/src/inspect/open-item.ts +22 -1
  84. package/src/inspect/session-list.ts +29 -0
  85. package/src/models/embedding-model.ts +114 -0
  86. package/src/models/transformers-version.ts +55 -0
  87. package/src/plugin/types.ts +3 -0
  88. package/src/portbroker/container-server.ts +23 -0
  89. package/src/portbroker/forward-request-bus.ts +35 -0
  90. package/src/portbroker/forward-result-bus.ts +2 -3
  91. package/src/portbroker/hostd-client.ts +182 -36
  92. package/src/portbroker/index.ts +6 -1
  93. package/src/portbroker/protocol.ts +9 -2
  94. package/src/run/channel-session-factory.ts +11 -1
  95. package/src/run/index.ts +41 -7
  96. package/src/server/command-runner.ts +24 -1
  97. package/src/server/index.ts +42 -8
  98. package/src/shared/index.ts +2 -0
  99. package/src/shared/protocol.ts +31 -0
  100. package/src/skills/typeclaw-channels/SKILL.md +4 -4
  101. package/src/skills/typeclaw-config/SKILL.md +2 -2
  102. package/src/skills/typeclaw-memory/SKILL.md +3 -1
  103. package/src/skills/typeclaw-permissions/SKILL.md +3 -3
  104. package/src/skills/typeclaw-skills/SKILL.md +1 -1
  105. package/src/skills/typeclaw-tunnels/SKILL.md +22 -1
  106. package/src/tunnels/providers/cloudflare-quick.ts +65 -7
  107. package/src/tunnels/upstream-probe.ts +25 -0
  108. package/typeclaw.schema.json +156 -67
  109. package/src/bundled-plugins/agent-browser/dashboard-discovery.ts +0 -170
  110. package/src/bundled-plugins/agent-browser/dashboard-proxy.ts +0 -421
  111. package/src/portbroker/bind-with-forward.ts +0 -102
@@ -1,23 +1,20 @@
1
1
  import { join } from 'node:path'
2
2
 
3
3
  import { definePlugin } from '@/plugin'
4
- import { bindWithForward } from '@/portbroker'
4
+ import { publishForwardRequest, subscribeForwardResult } from '@/portbroker'
5
5
 
6
- import { AGENT_BROWSER_DASHBOARD_PROXY_PORT, startDashboardProxy, type DashboardProxy } from './dashboard-proxy'
7
6
  import { installShim, KNOWN_BIN_PATHS, type InstallShimResult } from './shim-install'
8
7
 
9
8
  type SafeResult = InstallShimResult | { kind: 'error'; binPath: string; error: unknown }
10
9
 
11
10
  // Documented in skills/agent-browser/SKILL.md so the agent can discover which
12
- // port the proxy actually bound to (4848 + a 10-port fallback range). Moving
13
- // or renaming this path requires updating the skill in lockstep.
11
+ // host port the reserved dashboard forward actually bound. Moving or renaming
12
+ // this path requires updating the skill in lockstep.
14
13
  const PROXY_PORT_HINT_PATH = '/tmp/typeclaw-agent-browser-proxy-port'
15
- const PORT_CANDIDATE_RANGE = 10
16
- const BROKER_HANDSHAKE_DELAY_MS = 1_000
17
- const FORWARD_RESULT_TIMEOUT_MS = 10_000
14
+ const DASHBOARD_TARGET_PORT = 4848
15
+ const DASHBOARD_HOST_CANDIDATES = [4848, 4849, 4850, 4851, 4852, 4853, 4854, 4855, 4856, 4857] as const
18
16
 
19
- let activeProxy: DashboardProxy | null = null
20
- let bindingInFlight: Promise<void> | null = null
17
+ let unsubscribeForwardResult: (() => void) | null = null
21
18
 
22
19
  export default definePlugin({
23
20
  plugin: async (ctx) => {
@@ -25,20 +22,7 @@ export default definePlugin({
25
22
  logInstallResult(ctx.logger, safeInstallShim(binPath))
26
23
  }
27
24
 
28
- // Kick off the proxy bind in the background and let the plugin factory
29
- // return immediately. Two reasons:
30
- // 1. The container-side broker is created AFTER pluginsLoaded.markBooted()
31
- // runs (see src/run/index.ts). If we awaited bindWithForward here, we
32
- // would block the boot sequence past 20s of timeouts before the broker
33
- // even existed to send forward-result events.
34
- // 2. The dashboard isn't typically used at boot — the user runs
35
- // `agent-browser dashboard start` later. The proxy has plenty of time
36
- // to settle before its first request.
37
- if (activeProxy === null && bindingInFlight === null) {
38
- bindingInFlight = bindProxyAfterBrokerSettles(ctx.logger).finally(() => {
39
- bindingInFlight = null
40
- })
41
- }
25
+ requestDashboardForward(ctx.logger)
42
26
 
43
27
  return {
44
28
  skillsDirs: [join(import.meta.dir, 'skills')],
@@ -46,63 +30,35 @@ export default definePlugin({
46
30
  },
47
31
  })
48
32
 
49
- export function __resetProxyForTesting(): void {
50
- activeProxy?.stop()
51
- activeProxy = null
52
- bindingInFlight = null
53
- }
54
-
55
- export function __waitForProxyBindForTesting(): Promise<void> {
56
- return bindingInFlight ?? Promise.resolve()
33
+ export function __resetForwardRequestForTesting(): void {
34
+ unsubscribeForwardResult?.()
35
+ unsubscribeForwardResult = null
57
36
  }
58
37
 
59
- async function bindProxyAfterBrokerSettles(logger: {
60
- info: (msg: string) => void
61
- warn: (msg: string) => void
62
- }): Promise<void> {
63
- // Give the run-loop time to construct the container broker and let it
64
- // complete its WS handshake with hostd. Without this the first candidate
65
- // bind fires before the broker is ready, the bus never delivers a result,
66
- // and we waste the full timeout × candidate-count budget tearing down
67
- // every port in the range. The exact delay isn't load-bearing — anything
68
- // longer than the broker's connect+hello round-trip works.
69
- if (defaultBrokerEnabled()) {
70
- await Bun.sleep(BROKER_HANDSHAKE_DELAY_MS)
38
+ function requestDashboardForward(logger: { info: (msg: string) => void; warn: (msg: string) => void }): void {
39
+ if (!defaultBrokerEnabled()) {
40
+ recordProxyPort('TypeClaw dashboard forwarding unavailable: hostd broker is disabled.', logger)
41
+ return
71
42
  }
72
43
 
73
- const config = readPortConfig()
74
- const candidates = buildCandidatePorts(config.listenPort)
75
- const upstreamOverride = config.upstreamPort
76
-
77
- const result = await bindWithForward<DashboardProxy>({
78
- candidates,
79
- timeoutMs: FORWARD_RESULT_TIMEOUT_MS,
80
- factory: (port) => {
81
- try {
82
- const proxy = startDashboardProxy({ listenPort: port, upstreamPort: upstreamOverride })
83
- return Promise.resolve({ resource: proxy, close: () => proxy.stop() })
84
- } catch (error) {
85
- logger.warn(`bind ${port} failed: ${String(error)}`)
86
- return Promise.resolve(null)
44
+ if (unsubscribeForwardResult === null) {
45
+ unsubscribeForwardResult = subscribeForwardResult((event) => {
46
+ if (event.port !== DASHBOARD_TARGET_PORT) return
47
+ if (event.ok) {
48
+ recordProxyPort(String(event.hostPort), logger)
49
+ logger.info(`agent-browser dashboard forward reserved on host:${event.hostPort}`)
50
+ return
87
51
  }
88
- },
89
- onLog: (msg) => logger.info(`[bind-with-forward] ${msg}`),
90
- })
91
-
92
- if (result === null) {
93
- logger.warn(
94
- `could not allocate a host-forwardable dashboard proxy port from ${candidates[0]}-${candidates[candidates.length - 1]}; ` +
95
- `remote dashboard access will not work until another container releases its port`,
96
- )
97
- return
52
+ recordProxyPort(`TypeClaw dashboard forwarding unavailable: ${event.reason}`, logger)
53
+ logger.warn(`agent-browser dashboard forward failed: ${event.reason}`)
54
+ })
98
55
  }
99
56
 
100
- activeProxy = result.resource
101
- recordProxyPort(result.port, logger)
102
- logger.info(
103
- `dashboard proxy listening on port ${result.port}` +
104
- (result.hostPort !== null ? ` (forwarded to host:${result.hostPort})` : ''),
105
- )
57
+ publishForwardRequest({
58
+ targetPort: DASHBOARD_TARGET_PORT,
59
+ hostCandidates: [...DASHBOARD_HOST_CANDIDATES],
60
+ reason: 'agent-browser-dashboard',
61
+ })
106
62
  }
107
63
 
108
64
  function defaultBrokerEnabled(): boolean {
@@ -110,15 +66,9 @@ function defaultBrokerEnabled(): boolean {
110
66
  return token !== undefined && token.length > 0
111
67
  }
112
68
 
113
- function buildCandidatePorts(start: number): number[] {
114
- const out: number[] = []
115
- for (let i = 0; i < PORT_CANDIDATE_RANGE; i += 1) out.push(start + i)
116
- return out
117
- }
118
-
119
- function recordProxyPort(port: number, logger: { warn: (msg: string) => void }): void {
69
+ function recordProxyPort(contents: string, logger: { warn: (msg: string) => void }): void {
120
70
  try {
121
- Bun.write(PROXY_PORT_HINT_PATH, String(port))
71
+ Bun.write(PROXY_PORT_HINT_PATH, contents)
122
72
  } catch (error) {
123
73
  // Hint is informational (lets a future `typeclaw status` or a human shell
124
74
  // session report which port to open). Failure is non-fatal.
@@ -126,31 +76,6 @@ function recordProxyPort(port: number, logger: { warn: (msg: string) => void }):
126
76
  }
127
77
  }
128
78
 
129
- type PortConfig = { listenPort: number; upstreamPort: number | undefined }
130
-
131
- function readPortConfig(): PortConfig {
132
- const overrideUpstream = process.env['TYPECLAW_DASHBOARD_UPSTREAM_PORT']
133
- return {
134
- listenPort: numberFromEnv('TYPECLAW_DASHBOARD_PROXY_PORT', AGENT_BROWSER_DASHBOARD_PROXY_PORT),
135
- upstreamPort:
136
- overrideUpstream === undefined || overrideUpstream === '' ? undefined : numberOrUndefined(overrideUpstream),
137
- }
138
- }
139
-
140
- function numberOrUndefined(raw: string): number | undefined {
141
- const parsed = Number(raw)
142
- if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65_535) return undefined
143
- return parsed
144
- }
145
-
146
- function numberFromEnv(name: string, fallback: number): number {
147
- const raw = process.env[name]
148
- if (raw === undefined || raw === '') return fallback
149
- const parsed = Number(raw)
150
- if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65_535) return fallback
151
- return parsed
152
- }
153
-
154
79
  function safeInstallShim(binPath: string): SafeResult {
155
80
  try {
156
81
  return installShim({ binPath })
@@ -164,7 +89,7 @@ function logInstallResult(
164
89
  result: SafeResult,
165
90
  ): void {
166
91
  if (result.kind === 'installed') {
167
- logger.info(`installed agent-browser shim at ${result.binPath} (real bin: ${result.realBin})`)
92
+ logger.info(`installed agent-browser shim at ${result.binPath} (real bin stashed at ${result.stashTarget})`)
168
93
  return
169
94
  }
170
95
  if (result.kind === 'already-installed') {
@@ -172,7 +97,7 @@ function logInstallResult(
172
97
  return
173
98
  }
174
99
  if (result.kind === 'no-upstream') {
175
- logger.info(`no agent-browser binary at ${result.binPath}; skipping`)
100
+ logger.info(`no agent-browser binary at ${result.binPath}; nothing to shim here`)
176
101
  return
177
102
  }
178
103
  logger.warn(`failed to install agent-browser shim at ${result.binPath}: ${String(result.error)}`)
@@ -3,18 +3,11 @@
3
3
  // bash-regex `tool.before` block, which was leaky (missed shell variations,
4
4
  // bypassed by `typeclaw shell`, by spawned subprocesses, by the user typing
5
5
  // it directly). Now ANY in-container `agent-browser` caller routes through
6
- // here the dashboard subcommand transparently gets its --port rewritten
7
- // onto the agent-process-owned proxy's upstream port, every other subcommand
8
- // passes through unchanged. The proxy itself lives in the long-lived agent
9
- // process (see src/bundled-plugins/agent-browser/index.ts); the shim does NOT own its
10
- // lifecycle, because `agent-browser dashboard start` daemonizes upstream and
11
- // returns immediately — a shim-owned proxy would die the moment start exits.
6
+ // here and gets the anti-fingerprint User-Agent default before execing the
7
+ // real upstream binary unchanged.
12
8
 
13
9
  import { existsSync } from 'node:fs'
14
10
 
15
- import { writePortHint } from './dashboard-discovery'
16
- import { AGENT_BROWSER_DASHBOARD_UPSTREAM_PORT } from './dashboard-proxy'
17
-
18
11
  export const REAL_BIN_ENV = 'TYPECLAW_AGENT_BROWSER_REAL_BIN'
19
12
 
20
13
  // Recent desktop Chrome on Linux x86_64. The shim runs inside the TypeClaw
@@ -60,71 +53,6 @@ export function injectUserAgentEnv(
60
53
  env[USER_AGENT_ENV] = defaultUa
61
54
  }
62
55
 
63
- export type DashboardIntent = 'start' | 'stop' | 'other'
64
-
65
- export function classifyDashboardCommand(argv: readonly string[]): DashboardIntent {
66
- // Find the first non-flag token. `agent-browser` takes no pre-subcommand
67
- // global flags today; the loop is defensive against future ones.
68
- let dashboardIdx = -1
69
- for (let i = 0; i < argv.length; i += 1) {
70
- const arg = argv[i]!
71
- if (arg.startsWith('-')) continue
72
- if (arg !== 'dashboard') return 'other'
73
- dashboardIdx = i
74
- break
75
- }
76
- if (dashboardIdx === -1) return 'other'
77
-
78
- // Look for the next non-flag token after `dashboard`. Upstream treats a
79
- // missing subcommand as `start`, so we do too. `--port <n>` and `-p <n>`
80
- // consume two argv entries; the value is not a subcommand and must not be
81
- // classified as one.
82
- for (let i = dashboardIdx + 1; i < argv.length; i += 1) {
83
- const arg = argv[i]!
84
- if (arg === '--port' || arg === '-p') {
85
- i += 1
86
- continue
87
- }
88
- if (arg.startsWith('-')) continue
89
- if (arg === 'stop') return 'stop'
90
- if (arg === 'start') return 'start'
91
- return 'other'
92
- }
93
- return 'start'
94
- }
95
-
96
- export function rewriteDashboardArgs(argv: readonly string[], upstreamPort: number): string[] {
97
- // Force --port to upstreamPort regardless of what the caller passed. The
98
- // proxy on AGENT_BROWSER_DASHBOARD_PROXY_PORT (4848) is the only externally
99
- // visible surface; honoring a user --port would let a caller bypass the
100
- // proxy by listening directly on the externally forwarded port. Insert
101
- // `start` explicitly when the caller relied on the implicit-start behavior
102
- // so the appended `--port` lands on a subcommand upstream accepts.
103
- const stripped: string[] = []
104
- let i = 0
105
- while (i < argv.length) {
106
- const arg = argv[i]!
107
- if (arg === '--port' || arg === '-p') {
108
- i += 2
109
- continue
110
- }
111
- if (arg.startsWith('--port=')) {
112
- i += 1
113
- continue
114
- }
115
- stripped.push(arg)
116
- i += 1
117
- }
118
-
119
- const dashboardIdx = stripped.findIndex((a) => !a.startsWith('-'))
120
- const hasSubcommand = stripped.slice(dashboardIdx + 1).some((a) => !a.startsWith('-'))
121
- const out = hasSubcommand
122
- ? [...stripped]
123
- : [...stripped.slice(0, dashboardIdx + 1), 'start', ...stripped.slice(dashboardIdx + 1)]
124
- out.push('--port', String(upstreamPort))
125
- return out
126
- }
127
-
128
56
  export function resolveRealAgentBrowserBin(): string {
129
57
  // Set by the installer when it moves the upstream symlink aside. Honored
130
58
  // first so unit tests can point at a stub without touching the filesystem.
@@ -152,7 +80,6 @@ export function resolveRealAgentBrowserBin(): string {
152
80
  export type ShimOptions = {
153
81
  argv?: readonly string[]
154
82
  realBin?: string
155
- upstreamPort?: number
156
83
  spawn?: (cmd: string[]) => { exited: Promise<number> }
157
84
  env?: Record<string, string | undefined>
158
85
  }
@@ -160,29 +87,11 @@ export type ShimOptions = {
160
87
  export async function runShim(opts: ShimOptions = {}): Promise<number> {
161
88
  const argv = opts.argv ?? process.argv.slice(2)
162
89
  const realBin = opts.realBin ?? resolveRealAgentBrowserBin()
163
- const upstreamPort = opts.upstreamPort ?? AGENT_BROWSER_DASHBOARD_UPSTREAM_PORT
164
90
  const spawn = opts.spawn ?? defaultSpawn
165
91
  const env = opts.env ?? process.env
166
92
 
167
93
  injectUserAgentEnv(argv, env)
168
-
169
- const intent = classifyDashboardCommand(argv)
170
- if (intent !== 'start') {
171
- return await spawn([realBin, ...argv]).exited
172
- }
173
-
174
- // Record the rewritten port to the hint file so the long-lived proxy can
175
- // use it as the fast-path upstream lookup. The proxy still falls back to
176
- // procfs discovery if the hint is wrong, but the hint avoids that work
177
- // on the common path where the shim is the one starting the dashboard.
178
- try {
179
- writePortHint(upstreamPort)
180
- } catch {
181
- // Hint is an optimization; failure to write it is non-fatal.
182
- }
183
-
184
- const rewritten = rewriteDashboardArgs(argv, upstreamPort)
185
- return await spawn([realBin, ...rewritten]).exited
94
+ return await spawn([realBin, ...argv]).exited
186
95
  }
187
96
 
188
97
  function defaultSpawn(cmd: string[]): { exited: Promise<number> } {
@@ -38,8 +38,9 @@ The dashboard takes a moment to come up and the user needs time to open the URL.
38
38
  needed.)
39
39
  2. Read `/tmp/typeclaw-agent-browser-proxy-port` to learn the host-visible
40
40
  port. TypeClaw picks `4848` by default and falls back through `4849`–`4857`
41
- if another container is already on `4848`. If the file is missing, the proxy
42
- hasn't finished binding yet — wait a second and retry, or fall back to `4848`.
41
+ if another container is already on `4848`. If the file contains a diagnostic
42
+ instead of a number, forwarding is unavailable; report that message instead
43
+ of inventing a URL.
43
44
  3. Tell the user: **"Open `http://localhost:<port>` in your browser."** Over
44
45
  Tailscale or LAN, the same port works on the host's external address:
45
46
  `http://<host>:<port>`.
@@ -47,37 +48,11 @@ The dashboard takes a moment to come up and the user needs time to open the URL.
47
48
  5. When the user is done, they hand control back implicitly — just resume your
48
49
  normal `agent-browser` commands. Session state is shared with the dashboard.
49
50
 
50
- The compatibility proxy on `:4848` (or the fallback port) rewrites the
51
- dashboard's hardcoded loopback URLs so the externally visible URL works over
52
- Tailscale and other remote networks. No special flag, tool, or config required.
53
- **Always share the proxy port URL — never `localhost:<raw-session-port>`** —
54
- those raw ports are inside the container and unreachable from the host.
55
-
56
- ### Don't confuse the proxy port with the dashboard port
57
-
58
- There are two ports in play. Both sockets live inside the container, but
59
- they have very different audiences:
60
-
61
- | File | Audience | What it's for |
62
- | ------------------------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
63
- | `/tmp/typeclaw-agent-browser-proxy-port` | **Host browser** | The port the host browser opens (`http://localhost:<proxy-port>`). Host-forwarded via hostd; the compatibility proxy rewrites the dashboard's hardcoded loopback URLs so they work over Tailscale/LAN. **Default `4848`, falls back to `4849`–`4857` on collision.** |
64
- | `/tmp/typeclaw-agent-browser-upstream-port` | **In-container clients** | The actual `agent-browser dashboard` server. **Default `4849`.** This is what other in-container processes (Cloudflare tunnels, in-container `curl`, in-container scripts) must talk to. Not host-forwarded — there's no point. |
65
-
66
- **For Cloudflare tunnels and anything else that originates inside the
67
- container, use the upstream-port file, NOT the proxy-port file.** Cloudflare's
68
- `cloudflared` runs in the container's netns and connects to `127.0.0.1:<port>`
69
- directly — it doesn't traverse the compatibility proxy and gains nothing from
70
- it. Pointing a tunnel at the proxy port silently tunnels the proxy's listen
71
- socket instead of the dashboard; the tunnel comes up, the URL "works" against
72
- the proxy's pass-through paths, but anything dashboard-specific (sessions,
73
- WebSocket activity feed, JSON API) breaks in non-obvious ways.
74
-
75
- Common-failure shape: reading `proxy-port` mechanically because it's the
76
- file you remembered, then passing that to `typeclaw tunnel add` or to a
77
- `cloudflared --url http://127.0.0.1:<port>` invocation. **Read the
78
- `upstream-port` file for tunnel upstreams.** When in doubt, run
79
- `agent-browser dashboard status` (or check the `agent-browser dashboard
80
- start` log line — it prints the upstream URL).
51
+ The dashboard is served directly by `agent-browser` on one origin; TypeClaw only
52
+ reserves a host-forward for that port. No special flag, tool, or config is
53
+ required. **Always share the hint-file URL never
54
+ `localhost:<raw-session-port>`** — raw session ports are inside the container and
55
+ unreachable from the host.
81
56
 
82
57
  ### When NOT to use the dashboard
83
58
 
@@ -266,8 +266,8 @@ reports. For authored documents, stay on the Typst path above.
266
266
  ```
267
267
 
268
268
  Use a human-friendly `filename` and an absolute path. Slack, Discord, Telegram,
269
- and KakaoTalk upload the file; the GitHub adapter has no attachment support, so
270
- there post a link or paste the markdown.
269
+ and KakaoTalk upload the file; LINE and the GitHub adapter have no attachment
270
+ support, so there post a link or paste the markdown.
271
271
 
272
272
  - **Replying in a thread** — use `channel_reply` with the same `attachments` shape.
273
273
 
@@ -16,7 +16,13 @@ export async function isMemoryRetrievalCacheWriteAllowed(options: {
16
16
  }): Promise<boolean> {
17
17
  const { tool, args, agentDir, origin } = options
18
18
  if (tool !== 'write') return false
19
- if (origin?.kind !== 'subagent' || origin.subagent !== 'memory-retrieval') return false
19
+ // Allow the memory-retrieval subagent (existing path) OR the in-process
20
+ // vector retrieval writer (system origin with component='memory-retrieval').
21
+ // The in-process path runs under the session origin, not a subagent, so we
22
+ // check for the system component name as the trusted internal actor.
23
+ const isMemoryRetrievalSubagent = origin?.kind === 'subagent' && origin.subagent === 'memory-retrieval'
24
+ const isInProcessWriter = origin?.kind === 'system' && origin.component === 'memory-retrieval'
25
+ if (!isMemoryRetrievalSubagent && !isInProcessWriter) return false
20
26
 
21
27
  const rawPath = args.path
22
28
  if (typeof rawPath !== 'string') return false