PR #1810 retired the host-global gh token detection that injected GITHUB_TOKEN
into the request env, leaving both remote executors with a `_ = req` placeholder
to silence the unused parameter. resolveGHToken is now just a filter over the
selected credential IDs, so the parameter is gone from the Sprites and SSH
flavors alike. Test asserts the pass-through selection value, not just length.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(mcp): keep spawner attribution on a spawned session's first turn
A session started through spawn_session_kandev never learned who spawned
it, so it had no way to report back. The MCP handler did prepend a
<kandev-system> attribution block, but the first-turn canonicalizer
(sysprompt.canonicalizeKandevContext) drops every system block it cannot
attribute to server state — and the spawn block was not on the trusted
list, so it was deleted before the agent ever saw it. Spawns are always a
first turn, so this happened every time.
Generate the block at the launch site instead, from server-resolved
identity, and whitelist that exact content for the injector — the same
pattern entity references already use:
- config/prompts/spawned-session.md holds the attribution template.
- sysprompt.SpawnedSessionContext builds it; sysprompt.StripTags (lifted
out of the handlers' stripSystemTag) keeps embedded IDs from closing
the block early.
- LaunchSessionRequest.SpawnOrigin carries the spawner identity and is
kept off the wire (json:"-") so a WS client cannot forge a trusted
system block.
- startTask prepends the block and passes the identical content as
trusted content to both the task and Office injectors. Its trailing
env/route parameters move into a startTaskOptions struct rather than
growing the positional list again.
- The MCP handler now only fills SpawnOrigin (including the spawner's
session name) and passes the prompt through untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(mcp): derive spawn attribution from the verified spawner session
Addresses both PR review findings.
CodeRabbit: spawnOrigin copied sender_task_id / sender_session_id straight
out of the request, so an unknown or cross-task sender_session_id became
trusted launch attribution — the spawned agent would be told to report to
a session that never asked for it. Resolve the spawner session once, up
front, require it to belong to the claimed sender task, and derive the
origin (and the inherited agent profile) from that row. An unverifiable
claim now yields no attribution instead of a forged one. This also drops
a duplicate session lookup.
Codex: passthrough profiles skip the Kandev MCP wrap, so the new
attribution never reached a spawned passthrough session even though it
gets MCP configured and can reply. Give that path plain-text attribution
(no <kandev-system> markup, which would show up raw in the TTY the user
is watching). The wrap branch moves into applyLaunchPromptContext, which
keeps startTask under the nesting limit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Per-session events are published on a suffixed bus subject
("shell.output.<sessionId>") but stamped with the bare event-type constant
("shell.output"). The plugin deliverer subscribed with the manifest pattern and
then re-checked that pattern against event.Type, which made every suffixed
subject undeliverable: a 3-segment pattern matched the bus subscription but
failed the re-check on segment count, while a 2-segment pattern passed the
re-check but never subscribed to the 3-segment subject.
Carry the concrete publish subject on bus.Event (stamped in place by Publish in
both the memory and NATS implementations; the NATS receive side prefers the
delivering msg.Subject) and re-check the manifest pattern against it via a new
Event.EffectiveSubject helper that falls back to Type. The concrete subject is
also what plugins receive as pluginsdk.Event.EventType -- already documented in
the proto as "bus subject" -- so a plugin subscribed to "shell.output.*" can
read the session id off it.
Audited all 109 EventBus.Publish call sites: 17 subject families were affected;
office.run.event_appended, the office tree-hold subjects and the cross-plugin
plugin.<id>.<name> already stamped Type with the full subject. Three git.ws
sites in the orchestrator publish on git.ws.<sessionId> while stamping
events.GitEvent, so the subject there is not a suffixed form of its own type at
all -- left as-is, since the gateway routes off the subject and never reads
event.Type. Unsuffixed subjects publish with subject == type, so existing
2-segment subscriptions are unaffected, and the re-check still enforces manifest
segment-count strictness over the bus's looser NATS ">" semantics.
wsStatus, wsListInstances and wsTest discarded the ParsePayload error and
carried on with a zero-valued SecretID, so a malformed frame surfaced as a
confusing downstream failure (or a lookup against an empty secret ID) instead
of a clear bad-request. Match the sibling handlers in the same file and return
ws.NewError(..., ErrorCodeBadRequest, "invalid payload", nil).
Absent payloads keep working: Message.ParsePayload treats a nil payload as a
no-op, so only genuinely malformed JSON now errors.
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
All four templates were confirmed against the shipped CLI, not inferred
from the agent name:
- pi-acp -> {home}/.pi (pi-acp@0.0.32 package source)
- qoder-acp -> {home}/.qoder (qodercli 1.1.7, --config-dir proof)
- kiro-acp -> {home}/.kiro (Kiro CLI 2.15.0, ACP session/new)
- trae-acp -> {home}/.cache/trae-cli (Trae CLI 0.120.48, ACP session/new)
Kiro and Trae deliberately break the "{home}/.<agent>" shape: Kiro keeps
its login token in ~/.local/share/kiro-cli/data.sqlite3 while sessions
live in ~/.kiro, and Trae keeps config in ~/.trae but session state in
the cache dir with auth in the OS keyring. Each Runtime() records the
evidence and the residual gaps.
Extends the shared ACP agent spec table with sessionDirTemplate so every
agent's session root is pinned, including the ones already set.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
GetBlocking unconditionally returned an empty slice behind a TODO, so any
caller asking "which tasks does this task block?" silently got a wrong
answer. The reverse-lookup query the TODO asked for already exists on the
blocker repository as ListTasksBlockedBy (used by the office scheduler and
the handoff context builder) and is already part of the BlockerRepository
interface, so GetBlocking now delegates to it.
Like GetBlockers, it reports the raw blocker edges without filtering
archived tasks — every other ListTasksBlockedBy consumer does the same.
ListTasksBlockedBy also gains a deterministic ORDER BY (mirroring
ListTaskBlockers) and normalizes an empty result to a non-nil slice.
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`apps/web/lib/state/store.ts.backup` was a 1202-line snapshot of the
pre-slice-refactor monolithic Zustand store, accidentally committed in
b090a0157 (#1049). It never compiled and never ran — `tsconfig.json`
includes `**/*.ts`, which does not match a `.backup` suffix — so its only
effect was polluting grep/search results for anyone working on the real
store at `apps/web/lib/state/store.ts`.
No runtime code referenced it: `grep -rn "store.ts.backup" apps/` returns
nothing, and no build, lint, or tsconfig glob picks up `*.backup`.
Verified no logic was dropped. Of the 177 state keys and actions in the
backup, 172 exist in the live slice tree under `lib/state/slices/`. The
five that don't belong to two concepts that were deliberately renamed
after the snapshot was taken:
- `boardId` / `setBoards` / `setActiveBoard` — boards became workflows in
658bd349b (#228); the live kanban slice uses `workflowId` /
`setWorkflows` / `setActiveWorkflow`.
- `environmentsLoaded` / `setEnvironments` — environments became
executors; the live settings slice has `ExecutorsState` /
`setExecutors`.
Also add `*.backup`, `*.bak`, and `*.orig` to `.gitignore` so editor and
merge scratch copies cannot be committed again.
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
http.Get uses http.DefaultClient, which has no timeout and carries no
context. A backend that accepted the TCP connection but never responded
blocked waitForHealth forever, hanging the launcher with no way to
cancel it -- exactly what the suppressed noctx lint was warning about.
Probe through a package-level client with an explicit 2s timeout using
http.NewRequestWithContext, thread a context down from launcher.Run, and
drain each response body before closing so the connection is reused
across poll iterations. The overall wait now derives a deadline from the
caller's context, so cancellation ends it too.
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>