typeclaw 0.36.7 → 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.
- package/README.md +2 -2
- package/package.json +3 -2
- package/src/agent/index.ts +31 -11
- package/src/agent/live-sessions.ts +12 -0
- package/src/agent/model-fallback.ts +17 -15
- package/src/agent/model-overrides.ts +2 -2
- package/src/agent/session-meta.ts +10 -0
- package/src/agent/subagents.ts +11 -2
- package/src/agent/system-prompt.ts +9 -3
- package/src/agent/todo/continuation-policy.ts +6 -3
- package/src/agent/todo/continuation-wiring.ts +4 -2
- package/src/agent/todo/continuation.ts +3 -3
- package/src/agent/tools/todo/index.ts +27 -4
- package/src/bundled-plugins/agent-browser/index.ts +33 -108
- package/src/bundled-plugins/agent-browser/shim.ts +3 -94
- package/src/bundled-plugins/agent-browser/skills/agent-browser/SKILL.md +8 -33
- package/src/bundled-plugins/doc-render/skills/typeclaw-render-pdf/SKILL.md +2 -2
- package/src/bundled-plugins/guard/policies/memory-retrieval-cache-write.ts +7 -1
- package/src/bundled-plugins/memory/README.md +80 -23
- package/src/bundled-plugins/memory/append-tool.ts +74 -53
- package/src/bundled-plugins/memory/citation-superset.ts +4 -0
- package/src/bundled-plugins/memory/citations.ts +54 -0
- package/src/bundled-plugins/memory/dreaming-metrics.ts +30 -0
- package/src/bundled-plugins/memory/dreaming.ts +444 -21
- package/src/bundled-plugins/memory/index.ts +544 -400
- package/src/bundled-plugins/memory/load-memory.ts +87 -10
- package/src/bundled-plugins/memory/load-shards.ts +48 -22
- package/src/bundled-plugins/memory/memory-logger.ts +95 -106
- package/src/bundled-plugins/memory/memory-retrieval.ts +3 -3
- package/src/bundled-plugins/memory/parent-link.ts +33 -0
- package/src/bundled-plugins/memory/paths.ts +12 -0
- package/src/bundled-plugins/memory/references/frontmatter.ts +197 -0
- package/src/bundled-plugins/memory/references/load-references.ts +212 -0
- package/src/bundled-plugins/memory/references/store-reference-tool.ts +59 -0
- package/src/bundled-plugins/memory/search-tool.ts +282 -45
- package/src/bundled-plugins/memory/stream-events.ts +1 -0
- package/src/bundled-plugins/memory/stream-io.ts +28 -3
- package/src/bundled-plugins/memory/turn-dedup.ts +40 -0
- package/src/bundled-plugins/memory/vector/cache-write.ts +19 -0
- package/src/bundled-plugins/memory/vector/config.ts +28 -0
- package/src/bundled-plugins/memory/vector/doctor.ts +124 -0
- package/src/bundled-plugins/memory/vector/embedder.ts +246 -0
- package/src/bundled-plugins/memory/vector/hybrid.ts +439 -0
- package/src/bundled-plugins/memory/vector/index-on-write.ts +34 -0
- package/src/bundled-plugins/memory/vector/inspect.ts +111 -0
- package/src/bundled-plugins/memory/vector/passages.ts +125 -0
- package/src/bundled-plugins/memory/vector/reference-index-on-write.ts +50 -0
- package/src/bundled-plugins/memory/vector/relevance-gate.ts +93 -0
- package/src/bundled-plugins/memory/vector/startup.ts +71 -0
- package/src/bundled-plugins/memory/vector/store.ts +203 -0
- package/src/bundled-plugins/memory/vector/truncation.ts +124 -0
- package/src/bundled-plugins/security/policies/outbound-secret-scan.ts +2 -0
- package/src/channels/router.ts +239 -40
- package/src/cli/incomplete-init.ts +57 -0
- package/src/cli/init.ts +143 -12
- package/src/cli/inspect.ts +11 -5
- package/src/cli/model.ts +112 -34
- package/src/cli/restart.ts +24 -0
- package/src/cli/start.ts +24 -0
- package/src/cli/tunnel.ts +53 -8
- package/src/config/config.ts +110 -19
- package/src/config/index.ts +5 -1
- package/src/config/models-mutation.ts +29 -11
- package/src/config/providers-mutation.ts +2 -2
- package/src/config/providers.ts +146 -12
- package/src/container/shared.ts +9 -0
- package/src/container/start.ts +87 -4
- package/src/cron/consumer.ts +13 -7
- package/src/hostd/models.ts +64 -0
- package/src/hostd/paths.ts +6 -0
- package/src/hostd/portbroker-manager.ts +2 -2
- package/src/init/checkpoint.ts +201 -0
- package/src/init/dockerfile.ts +164 -51
- package/src/init/gitignore.ts +7 -7
- package/src/init/index.ts +41 -9
- package/src/init/line-auth.ts +50 -21
- package/src/init/models-dev.ts +96 -21
- package/src/init/oauth-login.ts +3 -3
- package/src/init/progress.ts +29 -0
- package/src/init/validate-api-key.ts +4 -0
- package/src/inspect/index.ts +13 -6
- package/src/inspect/item-list.ts +11 -2
- package/src/inspect/live-list.ts +65 -0
- package/src/inspect/open-item.ts +22 -1
- package/src/inspect/session-list.ts +29 -0
- package/src/models/embedding-model.ts +114 -0
- package/src/models/transformers-version.ts +55 -0
- package/src/plugin/types.ts +3 -0
- package/src/portbroker/container-server.ts +23 -0
- package/src/portbroker/forward-request-bus.ts +35 -0
- package/src/portbroker/forward-result-bus.ts +2 -3
- package/src/portbroker/hostd-client.ts +182 -36
- package/src/portbroker/index.ts +6 -1
- package/src/portbroker/protocol.ts +9 -2
- package/src/run/channel-session-factory.ts +11 -1
- package/src/run/index.ts +41 -7
- package/src/server/command-runner.ts +24 -1
- package/src/server/index.ts +42 -8
- package/src/shared/index.ts +2 -0
- package/src/shared/protocol.ts +31 -0
- package/src/skills/typeclaw-channels/SKILL.md +4 -4
- package/src/skills/typeclaw-config/SKILL.md +2 -2
- package/src/skills/typeclaw-memory/SKILL.md +3 -1
- package/src/skills/typeclaw-permissions/SKILL.md +3 -3
- package/src/skills/typeclaw-skills/SKILL.md +1 -1
- package/src/skills/typeclaw-tunnels/SKILL.md +22 -1
- package/src/tunnels/providers/cloudflare-quick.ts +65 -7
- package/src/tunnels/upstream-probe.ts +25 -0
- package/typeclaw.schema.json +156 -67
- package/src/bundled-plugins/agent-browser/dashboard-discovery.ts +0 -170
- package/src/bundled-plugins/agent-browser/dashboard-proxy.ts +0 -421
- 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 {
|
|
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
|
|
13
|
-
//
|
|
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
|
|
16
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
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,
|
|
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
|
|
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};
|
|
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
|
|
7
|
-
//
|
|
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
|
|
42
|
-
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
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
|
-
|
|
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
|