* chore(agentctl): delete duplicate procattr files in adapter package
setProcGroup and killProcessGroup are defined in both
agentctl/server/adapter/ and agentctl/server/process/. The process/
copies are the live ones (called by process/runner.go, manager.go,
vscode.go); the adapter/ copies have zero callers and are removed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(agentctl): remove orphan helpers from transport/shared
Left over from the ACP refactor. All had zero callers:
- DebugEnabled(): internal debugMode var is checked directly by
LogRawEvent/LogNormalizedEvent — the exported wrapper was unused.
- GetBool, GetMap, GetSlice: only GetString/GetInt are called in acp/normalize.go.
- NormalizeShellResult, NormalizeReadResult, NormalizeCodeSearchResult,
NormalizeModifyResult: normalization moved to acp/normalize.go.
- TruncateIfNeeded, MaxContentLength, MaxFileCount: only used by the
four Normalize* helpers above; removed transitively.
- TraceControlMessage: zero callers in the adapter transport layer.
GenerateUnifiedDiff, SplitLines, DetectLanguage, Tracer, ShutdownTracing,
TraceProtocolEvent, TraceProtocolRequest are alive and kept.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(agentctl): remove unused tool_payload constructors
NewHttpRequest, NewCreateTask, NewSubagentTask, NewShowPlan,
NewManageTodos, and NewMisc have zero callers — ACP normalization
builds NormalizedPayload via struct literals and JSON unmarshaling,
not these factory functions. NewReadFile, NewModifyFile, NewShellExec,
NewCodeSearch, and NewGeneric are kept (live callers in acp/normalize.go).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(agents/detect): remove dead fluent options and OSPaths type
WithFileExists, WithCommandOutput, WithEnvVar, expandHomePath (their
transitive helper), and OSPaths (struct + Resolve + Expanded) have
zero callers. Only WithCommand and WithNpxRunnable are used by agent
IsInstalled implementations. Imports trimmed to context and os/exec.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(office): remove dead channel methods superseded by service/
ListComments, DeleteComment, and UpdateChannelStatus on ChannelService
have zero callers — the live implementations live in
internal/office/service/comments.go and service/channels.go.
CreateComment is kept: it is called by HandleChannelInbound.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove scattered orphan helpers
Remove dead single-symbol exports verified to have zero callers:
- ParseCronExpression (office/shared/cron.go): NextCronTime covers
all live usages; the validator wrapper was unused.
- IsValidPathComponent, MustJSON (office/shared/helpers.go): zero
callers; GenerateSlug is kept.
- DiscoverUserSkillsAt (office/skills/userhome_discovery.go): exported
wrapper over the private discoverUserSkillsAt; only the internal
DiscoverUserSkills method is wired up.
- Manager.Get (agent/loginpty/manager.go): zero callers; GetByID and
Stop are kept (Stop is tested in manager_test.go).
- DetachedWithValues (common/appctx/appctx.go): zero callers; Detached
is kept.
- Repository.Close no-ops (task/share, terminal/repository): both are
documented no-ops with zero callers.
- TaskBranchName (worktree/config.go): zero callers; the deterministic
TaskBranchNameWithSuffix is kept.
Note: dialect.Now was initially targeted but is tested in
dialect_test.go (same-package call — missed by package-qualified grep).
It remains unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(share): remove stale Close reference from provider.go comment
Repository.Close() was removed as a no-op in the round-4 cleanup.
The comment in provider.go still referenced it by name; updated to
reflect that the repo simply doesn't own its DB connections.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(office/shared): delete helpers.go — GenerateSlug had zero callers
The triage step mistakenly identified skills/service.go as a caller,
but that package defines its own local GenerateSlug (different regex,
adds TrimSpace and a 'skill' fallback default). shared.GenerateSlug
was never called anywhere. helpers.go is now empty; delete the file.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(worktree): fix stale TaskBranchName reference in TaskBranchNameWithSuffix doc
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(share): route share links through gist.githack so big tasks render
gistpreview.github.io fetches gists via the public GitHub API, which returns
`content: ""` for files past GitHub's per-response content budget (~1 MB).
For non-trivial sessions, share.html and snapshot.json land past that
budget and gistpreview's `document.write(content)` renders a blank page.
gist.githack.com proxies the raw file content directly, so multi-MB
share.html renders correctly — at the cost of a one-time Cloudflare
anti-phishing interstitial on first visit, accepted as the price of
fidelity for big tasks.
displayURL now normalises bare gist URLs and unpinned githack URLs to the
canonical `gist.githack.com/<owner>/<id>/raw/share.html` form; legacy
gistpreview URLs pass through unchanged (the owner is not recoverable
from a gistpreview URL, so users can revoke + re-share to get the
githack form going forward).
* fix(share): address PR #1008 review feedback
- displayURL now re-pins legacy gistpreview URLs stored without /share.html
so they hit the styled view instead of silently falling back to the
alphabetically-first README.md (flagged by Greptile).
- Spec: unify two stale gistpreview references (API endpoint description
and the publish scenario) to the canonical githack form, with a note
that legacy gistpreview rows pass through. Flagged by CodeRabbit and
Claude — the spec is the public contract; one stale mention undermines
the migration.
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
* feat(share): add public share links via GitHub Gists
Adds a Share button to the chat panel header that publishes the
current session as a secret GitHub Gist on the user's account. The
share link routes through gistpreview.github.io to render share.html
as a styled kandev-themed page; snapshot.json + README.md ride along
for machine-readable + fallback views.
Snapshots run a redaction pass (absolute paths, common credential
shapes, .env contents, shell env vars) and strip <kandev-system>
prompt blocks via the shared sysprompt helper. Message filtering
whitelists user prose + tool_* types so internal noise never leaks
into shares.
Spec at docs/specs/public-share-links/spec.md.
* fix(share): coerce pendingClarification to bool for ClarificationSection
A late refactor extracted ClarificationSection with `pendingClarification: boolean`
on its props, but the call site passes the actual Message|null value. CI's tsc
build catches the mismatch; runtime was unaffected because the section only uses
the value in a truthiness check. Coerce with Boolean() at the call site to keep
the prop's intent narrow.
* fix(share): address PR #995 review feedback
Bugs (flagged by Greptile, Cubic, Claude):
- abs-path redaction split on `root + "/"` so sibling dirs like
/home/user/proj2 don't get corrupted when workspaceRoot is
/home/user/proj; regression test added.
- GHClient.DeleteGist now wraps "HTTP 404" stderr as *GitHubAPIError so
share.IsAlreadyGone matches consistently with the PAT client; this
restores the "gist already revoked upstream" soft-success path for
CLI-authenticated users.
- SnapshotSizeBytes is now measured from the same MarshalIndent payload
the gist backend uploads, so the size shown in the UI matches the
actual gist bytes (was ~20-40% underreported).
Polish (flagged by CodeRabbit, Cubic):
- Tool name + tool output now HTML-escaped; tool call args and output
render via <pre><code> instead of triple-backtick fences so payloads
containing ``` can't break out of the code block.
- share-list.tsx: clear `busy` via finally and break error layout out
of the justify-between row.
- use-shares.ts: convert UseSharesResult to interface (repo TS rules);
reset error+isLoading when ids are missing.
- use-shares.test.tsx: wait on the actual async outcome (shares
populated, listShares called) instead of isLoading flipping false.
- provider.go: replace `repo.Close` cleanup with an explicit noop
closure so the comment ("Cleanup is a no-op") and the impl match.
- spec.md: aligned the shareability rule across What/Failure/Scenarios/
Out-of-scope — any non-prehistory state can be shared; added language
tags to fenced code blocks for markdownlint.
Skipped intentionally (reply on each thread):
- HTML-escape every metadata badge: the values are controlled enum
strings from kandev's own schema, not user input. Defense-in-depth
noise. Tool name (which can come from agent runtime) IS escaped.
- useShares request-token guard against stale responses: the dialog
owns the hook lifecycle and opens against a single (taskId,
sessionId) — the race the comment describes can't occur here.
* fix(share): redact paths for every worktree, not just the first
A single TaskSession can carry multiple worktrees when the task spans
multiple repos. The redactor previously only saw the first worktree
path, so absolute paths from secondary worktrees leaked into public
shares instead of being rewritten to repo-relative form.
NewRedactor is now variadic and applies the abs-path rule against every
root it receives, sorted longest-first so a root that is a prefix of
another (e.g. /workspace vs /workspace/repo1) gets matched by the more
specific one first. workspaceRootFor in builder.go becomes
workspaceRootsFor and emits every WorktreePath plus the WorkspacePath
fallback.
Flagged by Cubic on PR #995.
* fix(share): address PR #995 review round 3
Bugs (flagged by Cubic + Claude on gh_client.go:714):
- DeleteGist now calls the existing isNotFoundErr helper instead of an
inline strings.Contains(err, "HTTP 404"). The helper also covers
"404 Not Found" and "status: 404", which some gh versions emit, so
the share service's "gist already revoked upstream" soft-success
path now fires consistently across CLI auth users.
Bugs (flagged by Claude on builder.go:220):
- toolCallBlock used to read metadata["tool_name"] and metadata["args"]
but production never writes those keys; the typed payload lives under
metadata["normalized"] (a *streams.NormalizedPayload) and the tool
kind is on Message.Type. Switched to deriving ToolName from
strings.TrimPrefix(m.Type, "tool_") (gives "read", "execute", "edit",
…) and reading args from metadata["normalized"], with backward-compat
fallback to the older "tool_name"/"args" keys for the debug-fixture
replay path. New TestBuildSnapshot_ToolCall_ProductionMetadataShape
exercises the real session shape.
Bugs (flagged by CodeRabbit on redaction.go:110):
- Bare workspace-root occurrences (pwd output, end-of-sentence, quoted
paths) used to leak because the matcher only handled root + "/".
Switched to a precompiled regex per root —
regexp.QuoteMeta(root) + `(?:/|\b)` — so both `root/...` and standalone
`root` get stripped, while sibling dirs like /workspace2 still don't
match. Test TestRedactor_AbsPath_StandaloneRootStripped covers the
bare-root variants.
Doc (flagged by Claude on spec.md):
- Two malformed fence closers (`go and `text on closing fences) corrected
to plain ``` so the file is markdownlint-clean.
* fix(share): narrow abs-path boundary to avoid sibling-prefix corruption
Round 4 review (Claude + CodeRabbit) caught a regression in the regex
introduced by 436cb66: `(?:/|\b)` used `\b` (word boundary) to catch
bare-root cases, but `\b` fires on any word↔non-word transition. So a
sibling path like `/workspace.bak`, `/workspace-old`, or
`/workspace-other` would false-match the `/workspace` root (since
e→. and e→- are word boundaries), stripping it and leaving a
corrupted `.bak` / `-old/file.ts` in the share.
Replaced the matcher with `(/|\s|$)`: path separator, whitespace, or
end-of-string only — never punctuation or quote chars. The boundary
is captured and re-emitted via ReplaceAllStringFunc (or dropped, in
the `/` case) so prose loses only the root, not the surrounding space.
Added TestRedactor_AbsPath_SiblingsWithPunctuationNotCorrupted covering
`.bak`, `-old`, `_alt` siblings; the digit-sibling test still passes.
The quoted-bare-root case (`"/workspace"`) no longer matches and was
dropped from the standalone-root test — contrived enough that a small
leak there is preferable to corrupting realistic sibling paths.
* refactor(share): move share button into chat status bar
Replaces the dedicated PanelHeaderBarSplit above the message list with
a slot in ChatStatusBar (the existing row holding TodoIndicator and
PRStatusChip just above the chat input). The button anchors right via
ml-auto; when the "proceed to next step" affordance is present it
takes the right anchor instead and the share button stays inline.
Also tightens the iconOnly variant in ShareButton to a ghost style
that matches the muted-foreground chips in the status bar, and exports
shareableSessionStateClient from share-button.tsx so the gating logic
lives next to the entry point it controls.
Net -23 lines in task-chat-panel.tsx: the extra header bar and the
local gate helper are gone.
* docs(share): broaden tooltip + add share/ to CLAUDE.md tree
The share tooltip read "Share this completed task with a public link",
but shareableSessionStateClient allows RUNNING / IDLE / WAITING_FOR_INPUT
/ FAILED / CANCELLED too. A user hovering mid-run saw misleading copy;
reworded to "Share this task conversation as a public link" which
matches the actual gate.
Also added the missing internal/task/share/ entry to the task/ subtree
in CLAUDE.md so the new package shows up in the repo map — flagged
across multiple rounds.
* docs: add internal/task/share/ to repo map
Carryover from the share PR — AGENTS.md (which CLAUDE.md symlinks to)
was missing the share/ subpackage under task/. Adding it now so the
repo map matches what's checked in.
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>