Commit 55b4cc8
Chore/untrack internal notes (#40)
* fix(ws): drop CurrentTime from heartbeat env info
CurrentTime was captured once via collectEnvironmentInfo() at runner
start and the resulting EnvInfo was sent only on the first heartbeat
(envInfoSent gate), so a long-lived runner kept reporting its boot
time forever. Safari then served that stale value via the `now` tool.
Static info (OS, arch, hostname, timezone) is fine to send once;
current time is by definition not static. Drop the field entirely —
safari uses its own wall clock now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ws): tighten reconnect timing for cloud sandbox pool pods
Cloud sandbox pool pods spend their entire pre-claim lifetime dialing
safari and getting 401 (no t_cloud_sandbox_0 row exists yet — claim
inserts it). Two timing knobs were tuned for long-lived BYOC outage
recovery and hurt cloud claim latency:
- initialReconnectDelay 1s -> 200ms: first dials race the row INSERT.
The 1s delay added 1-2s of extra 401-retry cost before hostname
auth could possibly succeed.
- maxReconnectDelay 5min -> 10s: a pool pod waiting >50s slid into a
32-64s exponential-backoff sleep, and got declared unrecoverable by
safari's 90s claim window before its next dial — observed live
(sbx_bzQ... did not come online within 1m30s, traceid
6996857e714cb99c31665d043ed3c260). 10s stays well inside the claim
window and remains acceptable for BYOC server-restart recovery.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(env): reject duplicated-root rel_paths; surface sibling hint on ENOENT
safePath defense-in-depth — runner cannot rely on safari to filter rel_paths
that mirror the workspace root. Joining e.root with such a path silently
created a nested duplicate workspace tree (matching nested copies were
observed on a long-running BYOC host). Reject them at the runner boundary.
Read now appends a bounded sibling-name list when stat returns ENOENT, so
the agent can self-correct a near-miss filename without spending a turn on
an extra list call.
.golangci.yml: exclude gosec G706 (log-taint via taint analysis). slog's
structured key-value logging is not a format-string injection vector; the
rule fires on every slog call with a user-controlled value, which is the
whole point of structured logging.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(skill): probe-or-install sync_skill; default home ~/.flashduty
- SyncSkill now checks .checksum+SKILL.md before install (cache hit
returns {Cached:true,Path}; miss with no zip returns {Cached:false}
so cloud retries with zip_data)
- Skills land at <home>/skills/<name>/ instead of <home>/.work/skills/<name>/
- Default home moves from ~/.flashduty-runner/workspace to ~/.flashduty
- FLASHDUTY_RUNNER_HOME is the new canonical override; FLASHDUTY_RUNNER_WORKSPACE
kept as deprecated alias
- Add SyncSkillResult.Cached field (omitempty); SyncSkillArgs unchanged
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(test): extract resolveDir helper, use errors.Is for ErrNotExist
- resolveDir(t, dir) replaces duplicated EvalSymlinks boilerplate in
ProbeHit and InstallOverwrites tests
- errors.Is(err, os.ErrNotExist) replaces deprecated os.IsNotExist pattern
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* harden(skill): reject path separators in skill_name
* docs(skill): document ~/.flashduty home and probe-or-install sync_skill
* refactor(env,permission): SSRF + AST hardening + grep regex + bash structured
Runner-side counterparts of fc-safari's 2026-05-02 builtin-tools refactor:
- environment/webfetch: SSRF guard via inlined safeHTTPTransport +
safeCheckRedirect. Pre-flight validateURL refuses RFC1918 / loopback
/ link-local / IMDS before any TCP dial; CheckRedirect re-validates
each hop so a 302 to 169.254.169.254 is refused. Inlined from
go-pkg/x/netsafe (open-source repo cannot import internal modules).
- environment/htmlx: HTML-to-markdown / text helpers inlined from
go-pkg/x/htmlx for the same reason. html-to-markdown is now a
direct dep.
- environment/environment::unzipSkill: zip-slip closed — compares
abs target against dest+separator and rejects names containing '..'.
- environment/environment::grepWithGo: was strings.Contains (literal)
— now compiles regex, falls back with regex_compile_error surfaced.
Default ignore (.git, node_modules, .flashduty) added; Scanner
buffer raised to 1 MiB; rg exit-2 surfaces real I/O errors instead
of silent "no matches"; pattern starting with '-' gets '--' prefix;
rsplit on ':' so paths containing ':' parse cleanly.
- protocol.GrepArgs gains output_mode, context_before/after,
head_limit, file_type, case_sensitive — forwarded to ripgrep flags.
- environment/environment::Bash: per-stream large-output (stderr now
truncates to its own bash_stderr_*.txt); LimitedWriter honors the
io.Writer contract (returns ErrOutputCapped at the cap, exposes
Hit(), appends "[output capped at 10MB]" marker once).
- protocol.BashResult carries truncated_stdout/stderr,
stdout_file_path/stderr_file_path, *_total_size; legacy fields
remain populated so old safari clients keep working.
- environment/large_output::ShouldSkipForOutputsDir: substring match
→ word-boundary regex (no more 'thread '/'head ' false positives).
WriteRaw lowercased to writeRaw (single internal caller).
- permission/permission: AST walk now visits CmdSubst / ProcSubst /
nested Stmts so cat <(curl evil) and echo $(curl evil) hit the
same rules. Redirect targets are checked instead of discarded
(cmd > /etc/passwd refused). Canonical-form normalization on both
rule and command sides (kubectl get pods matches kubectl get *);
env-prefix dual evaluation (KUBECONFIG=x kubectl get pods still
matches kubectl get *). Rules sort by literal-prefix specificity,
first match wins. SafeReadOnlyRules drops echo */find * — find
-delete / find -exec rm now denied by default; replaced with
scoped allow patterns.
E2E verified live: bash returns the new structured shape, web
(action=fetch) reaches example.com via the safe transport, all 17+
new permission/permission_test scenarios pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(lint): clear golangci-lint findings + add /health endpoint
- netsafe: guard http.DefaultTransport type-assertion (errcheck)
- environment: switch if-else chain to switch (gocritic), drop dead err=nil (ineffassign), rename shadowed err vars (govet)
- permission: syntax.ClbOut → syntax.RdrClob (staticcheck SA1019)
- cmd: add /health listener for Tencent AGS readiness probe; bind via ListenConfig.Listen with parent context (noctx) and document the all-interfaces bind (gosec G102)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ci): drop invalid G706 gosec exclude + handle unix-abs zip-slip on windows
- .golangci.yml: pinned CI golangci-lint v2.4 rejects G706 under
gosec.excludes (rule unknown to its gosec). Move suppression to the
version-safe linters.exclusions.rules text match so both v2.4 and newer
versions stay quiet. Same regression previously fixed in a60df85.
- environment/unzipSkill: filepath.IsAbs("/etc/passwd") returns false on
Windows (no drive letter), so the absolute-path guard let the entry
slip through and TestUnzipSkill_RejectsZipSlip/absolute-path-unix
failed on windows-latest. Reject leading "/" or "\\" on the raw name
before Clean normalizes separators.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(knowledge): reconcile_knowledge_manifest RPC for orphan/stale prune + missing-file diff
Adds a per-session RPC so Safari can declare its current view of which
knowledge files should exist (manifest = list of rel_path + sha256). The
runner walks <root>/knowledge/<scope>/, deletes anything not in the
manifest (orphan prune), deletes anything whose sha disagrees with the
sentinel (stale prune), and returns NeedsStage = manifest entries it
doesn't have on disk after the prune step.
Safari uses NeedsStage to drive a follow-up bulk StageKnowledgeFiles so
the full pack lands at session start instead of lazily on first read.
This is what makes ~/.flashduty/knowledge/<scope>/ look like the actual
pack contents (DUTY.md + every runbook) instead of just whatever the LLM
happened to read in the current turn.
Walks the on-disk tree (rather than trusting the sentinel) so manual
operator drops also get reconciled instead of lingering forever. Reuses
the existing sentinel + advisory lock so concurrent stage calls from the
same Safari are safe.
Pairs with fc-safari change wiring BuildManifest +
ReconcileKnowledgeManifest + EagerStageMissing into mw_env.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 078d107 commit 55b4cc8
4 files changed
Lines changed: 486 additions & 52 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
16 | 23 | | |
17 | 24 | | |
18 | 25 | | |
| |||
21 | 28 | | |
22 | 29 | | |
23 | 30 | | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
29 | 42 | | |
30 | 43 | | |
31 | 44 | | |
32 | 45 | | |
33 | | - | |
34 | | - | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
35 | 56 | | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
| 57 | + | |
| 58 | + | |
40 | 59 | | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
45 | 68 | | |
46 | 69 | | |
47 | 70 | | |
| |||
166 | 189 | | |
167 | 190 | | |
168 | 191 | | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
169 | 198 | | |
170 | 199 | | |
171 | 200 | | |
| |||
197 | 226 | | |
198 | 227 | | |
199 | 228 | | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
200 | 361 | | |
201 | 362 | | |
202 | 363 | | |
| |||
0 commit comments