feat(tess): add generic interaction CLI #731
Reference in New Issue
Block a user
Delete Branch "feat/tess-cli"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Refs #709
Task: TESS-M3-002 / TESS-CLI-001
Adds a configured-name-driven interaction CLI (chat, status, sessions, tree, attach, send, stop, health, recover) and authenticated gateway boundary. Session mutations derive actor scope server-side, use durable identity/recovery, route through the provider registry, and require exact-action approval for stop.
Name-as-config: command surface is
mosaic interaction; the instance is supplied by --agent or MOSAIC_AGENT_NAME. No instance-specific command literal is hardcoded; tests cover Nova.Verification: fresh node_modules install; pnpm turbo run typecheck lint test --force; pnpm typecheck; pnpm lint; pnpm format:check; pnpm test.
Independent review — PR #731
feat(tess): add generic interaction CLIReviewer: [W-jarvis:reviewer-sonnet] (independent, non-author of LANE diskhygiene-terra)
Head reviewed:
de74e46a0640c44c6b7294c24669496980761c92(confirmed via API immediately before review and unchanged throughout)1. Subcommand surface — VERIFIED
All nine required verbs are registered and each does real work against the gateway API (no stubs):
packages/mosaic/src/commands/interaction.ts:64status→fetchInteractionStatusinteraction.ts:73health→fetchInteractionHealthinteraction.ts:80sessions <provider>→fetchInteractionSessionsinteraction.ts:94tree <provider>→fetchInteractionTreeinteraction.ts:106attach <sessionId>→attachInteractionSessioninteraction.ts:121send <sessionId> <message>→sendInteractionMessageinteraction.ts:146chat <sessionId> <message>→sendInteractionMessage(conversational alias ofsend, same real dispatch path — not a stub)interaction.ts:170stop <sessionId>→stopInteractionSession,--approvalrequiredinteraction.ts:187recover <sessionId>→recoverInteractionSessionTest
packages/mosaic/src/commands/interaction.test.ts:6-21asserts exactly these 9 subcommands are registered.Non-blocking nit:
interaction.ts:120-143buildsconst send = …then immediately doesvoid send;purely to silence an unused-var lint rule — harmless but a little odd; a plain statement without the binding would read cleaner.2. Reuse of merged M1/M2 (no re-invention) — VERIFIED
recover→apps/gateway/src/agent/interaction.controller.ts:137-156callsTessDurableSessionService.recoverProviderSession, which calls the pre-existingDurableSessionCoordinator.recover(tess-durable-session.service.ts:79-83) — the M2 durable coordinator, not a new mechanism.attach→interaction.controller.ts:63-85obtains identity viadurable.getSnapshot(new thin wrapper added in this PR attess-durable-session.service.ts:67-77, itself built from the pre-existingcoordinator.snapshot+assertScope) then callsRuntimeProviderService.attach, the same registryexecute()path used by every other runtime verb (runtime-provider-registry.service.ts:216-231).send/stop→send(interaction.controller.ts:87-112) usesqueueProviderSend+dispatchProviderOutbox(pre-existing M2 outbox/idempotency path,tess-durable-session.service.ts:26-63), which internally callsRuntimeProviderService.sendMessage.stop(interaction.controller.ts:114-135) callsRuntimeProviderService.terminate, which internallyconsume()s a durable exact-action approval (runtime-provider-registry.service.ts:251-276) before ever reaching the provider — a hard 403 (ForbiddenException('Exact-action approval is required'),interaction.controller.ts:123-124) if--approvalis missing. No ad-hoc bypass found;sendis not gated by approval, which is consistent with the pre-existing registry design (onlyterminateconsumes an approval token) and is not something this PR changed.assertConfiguredAgentthendurable.getSnapshot/assertSessionAgentbefore touching the runtime (interaction.controller.ts:42,56,71,78,95,101,122,127,144,147), andgetSnapshot/queueProviderSend/dispatchProviderOutboxall funnel through the existingassertScopecheck comparingcontext.actorScopeto the durable session'sownerId/tenantId(tess-durable-session.service.ts:88-96).3. Credential-safe status/health — VERIFIED
status(interaction.ts:64-69) hits the pre-existing/api/providers/statusendpoint, unmodified by this PR (apps/gateway/src/agent/providers.controller.ts:38-48), which already reduces provider errors to a stableerrorCodeand exposeseffectivePolicyviagetEffectivePolicyStatus()— the M2-001 credential-safe pattern, reused rather than reinvented.health(interaction.ts:73-77) hits the pre-existing global/health/ready(apps/gateway/src/health/health.controller.ts:11-14), which returns only{status:'ready'}— no config/credential surface by construction.interaction.ts,interaction.test.ts,gateway-api.tsadditions,interaction.controller.ts) for secret/token/credential literals — none found; only session cookies are forwarded as opaque headers, never logged or printed.4. NAME-AS-CONFIG invariant — VERIFIED
packages/mosaic/src/commands/interaction.ts:21-25(configuredAgentName) derives the instance identity solely from--agentorMOSAIC_AGENT_NAME; it throws if neither is set. No literaltesscommand or identity appears anywhere in the new CLI/controller code.apps/gateway/src/agent/interaction.controller.ts:176-181(assertConfiguredAgent) rejects any request whose URL:agentNamedoesn't match the server's ownMOSAIC_AGENT_NAMEenv var — this is what prevents a client from spoofing a different configured identity, not a hardcoded name check.apps/gateway/src/agent/interaction.controller.test.ts:7-22setsMOSAIC_AGENT_NAME='Nova', callscontroller.sessions('Nova', …)and gets a real result, then callscontroller.sessions('Other', …)and gets rejected — proving the exact generic/config-driven behavior required, using a non-tessinstance name.grep -in tess packages/mosaic/src/commands/interaction.ts packages/mosaic/src/commands/interaction.test.ts packages/mosaic/src/tui/gateway-api.ts→ zero hits. Inapps/gateway/src/agent/interaction.controller.tsthe only hits are the imported class nameTessDurableSessionService(a pre-existing M2 service/class name, not an identity/command literal or config default — the controller never comparesagentNameto'tess'or any hardcoded string). No FAIL condition found.5. Quality gate — VERIFIED
Ran directly in an isolated worktree at the reviewed head (after building the workspace's transitive TS project deps, which is an environment/build-order prerequisite unrelated to this PR's content):
pnpm --filter @mosaicstack/gateway exec vitest run→ 572 passed, 11 skipped (integration tests requiring live services), 0 failed (47 files), including the newsrc/agent/interaction.controller.test.ts(4/4 passed).pnpm --filter @mosaicstack/mosaic exec vitest run→ 640 passed, 0 failed (47 files), including the newsrc/commands/interaction.test.ts(1/1 passed).tsc --noEmitclean for both@mosaicstack/gatewayand@mosaicstack/mosaic.eslintclean on all touched/added files in both packages.ci/woodpecker/pr/ci) state at headde74e46a…= success (https://ci.mosaicstack.dev/repos/47/pipeline/1735/1), confirmed via the Gitea status API immediately before posting this verdict, head unchanged since review start.Blocking findings
None.
Non-blocking follow-ups (do not gate this PR)
interaction.ts:143(void send;) — cosmetic; consider dropping the unused intermediate binding.chatandsendare functionally identical today (both callsendInteractionMessage); fine per the required subcommand list, but if they're meant to diverge later (e.g.chatadding conversational framing) it's worth a code comment noting the intentional duplication.VERIFIED APPROVE reviewer-of-record [W-jarvis:reviewer-sonnet] head
de74e46a06VERIFIED APPROVE reviewer-of-record [W-jarvis:reviewer] head
de74e46a06Evidence verified at exact head
de74e46a0640c44c6b7294c24669496980761c92:1735is terminal green on this commit; stepsci-postgres,install,sanitization,typecheck,lint,format, andtestsucceeded.mosaic interactioncommand registers required verbs: chat/status/sessions/tree/attach/send/stop/health/recover; instance identity is data via--agentorMOSAIC_AGENT_NAME.@CurrentUser()/scopeFromUser, requiresX-Correlation-Id, validates configured agent identity, and checks durable session identity before attach/send/stop/recover.No merge performed by reviewer.
Marker: ROR-731-APPROVE-de74e46a