Skip to content

Fix chat/send userId attribution + eliminate duplicate browser tabs#387

Merged
joelteply merged 1 commit into
mainfrom
fix/phase0-bugs
Mar 25, 2026
Merged

Fix chat/send userId attribution + eliminate duplicate browser tabs#387
joelteply merged 1 commit into
mainfrom
fix/phase0-bugs

Conversation

@joelteply
Copy link
Copy Markdown
Contributor

Summary

  • chat/send userId bug — personas can't post to chat rooms #376 fix: chat/send without explicit senderId now resolves to the seeded human owner. Previously resolved to "Claude Code" (agent detection) or "@cli" depending on how jtag was invoked. Before/after visible in chat export: message #777bb2 shows "Claude Code", message #5a1c5b shows "Joel".
  • Multiple browser tabs launched on npm start #335 fix: Removed Phase 6 browser launch from parallel-start.sh. The SystemOrchestrator.detectAndManageBrowser() already handles browser lifecycle — the shell script was racing it and opening a duplicate tab on every cold start.

Test plan

  • npm run build:ts — compiles clean
  • npm start — deploys successfully
  • ./jtag collaboration/chat/send --room="general" --message="test" — shows senderName: "Joel", senderType: "human"
  • AI personas respond to the message
  • Verify only one browser tab opens on cold start

Closes #376, closes #335

#376: chat/send without explicit senderId now resolves to the seeded
human owner instead of the session identity (which was "@cli" or
"Claude Code" depending on how jtag was invoked). Single-owner system:
CLI commands are the human's tool, messages should be attributed to them.
Also fix CLI session resolution in SessionDaemonServer to prefer the
human owner over creating a separate @cli user.

#335: Remove Phase 6 browser launch from parallel-start.sh. The
SystemOrchestrator.detectAndManageBrowser() already handles browser
lifecycle — the shell script was racing it and opening a duplicate tab
on every cold start.
Copilot AI review requested due to automatic review settings March 25, 2026 01:47
@joelteply joelteply merged commit 8eec4ab into main Mar 25, 2026
4 of 6 checks passed
@joelteply joelteply deleted the fix/phase0-bugs branch March 25, 2026 01:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to (1) fix incorrect sender attribution for chat/send-style message injection by defaulting to the seeded human owner, and (2) prevent duplicate browser tabs by removing browser-launch logic from parallel-start.sh in favor of the orchestrator-managed lifecycle.

Changes:

  • Resolve CLI sessions to the seeded human owner (with a fallback to the prior uniqueId-based behavior when no owner exists yet).
  • Change collaboration/chat/send default sender resolution to prefer an explicit senderId, otherwise resolve a human owner.
  • Remove shell-script browser launching to avoid racing SystemOrchestrator.detectAndManageBrowser().
  • Regenerate command registries/constants/schemas (new commands added to generated outputs).

Reviewed changes

Copilot reviewed 7 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/commands/collaboration/chat/send/server/ChatSendServerCommand.ts Changes default sender resolution; adds helper to resolve human owner fallback.
src/daemons/session-daemon/server/SessionDaemonServer.ts Updates CLI session identity mapping to prefer seeded owner.
src/scripts/parallel-start.sh Removes Phase 6 browser open logic; keeps hot-restart reconnect wait.
src/shared/generated-command-constants.ts Regenerated command constant list with additional commands.
src/server/generated.ts Regenerated server command registry with additional commands.
src/browser/generated.ts Regenerated browser command registry with additional commands.
src/generated-command-schemas.json Regenerated command schema metadata with additional commands.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +45 to 52
// 2. Get sender — explicit senderId takes priority, otherwise resolve human owner.
// params.userId reflects the session identity (could be @cli, agent, system).
// For chat/send without explicit sender, the human owner is the correct attribution
// since this is a single-owner system and CLI/agent are the human's tools.
const sender = params.senderId
? await this.findUserById(params.senderId, params)
: await this.findHumanOwnerOrFallback(params);

Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chat/send now overrides the sender to the first type:'human' user whenever senderId is omitted. This breaks the documented behavior (“defaults to current user from session”) and will misattribute messages in multi-user scenarios (e.g., test clients connected as different humans) because their session userId is ignored.

Suggestion: keep params.userId as the default sender, and only remap to the seeded owner when the session user is a tool/placeholder identity (e.g., user.type !== 'human', or uniqueId is '@cli', or uniqueId starts with 'anon-').

Suggested change
// 2. Get sender — explicit senderId takes priority, otherwise resolve human owner.
// params.userId reflects the session identity (could be @cli, agent, system).
// For chat/send without explicit sender, the human owner is the correct attribution
// since this is a single-owner system and CLI/agent are the human's tools.
const sender = params.senderId
? await this.findUserById(params.senderId, params)
: await this.findHumanOwnerOrFallback(params);
// 2. Get sender
// - Explicit senderId takes priority.
// - Otherwise, default to the session user (params.userId) when it is a real human.
// - Only remap to the seeded human owner when the session user is a tool/placeholder
// identity (e.g., user.type !== 'human', uniqueId === '@cli', or uniqueId.startsWith('anon-')),
// or when there is no session user.
let sender;
if (params.senderId) {
sender = await this.findUserById(params.senderId, params);
} else if (params.userId) {
const sessionUser = await this.findUserById(params.userId as UUID, params);
const sessionEntity = sessionUser.entity;
const uniqueId = (sessionEntity as any).uniqueId as string | undefined;
const isPlaceholderIdentity =
sessionEntity.type !== 'human' ||
uniqueId === '@cli' ||
(typeof uniqueId === 'string' && uniqueId.startsWith('anon-'));
sender = isPlaceholderIdentity
? await this.findHumanOwnerOrFallback(params)
: sessionUser;
} else {
sender = await this.findHumanOwnerOrFallback(params);
}

Copilot uses AI. Check for mistakes.
Comment on lines +147 to +157
limit: 1,
context: params.context,
sessionId: params.sessionId,
});

if (result.success && result.items && result.items.length > 0) {
const owner = result.items[0];
return { id: owner.id, entity: owner };
}

// No human owner seeded yet — fall back to session userId
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

findHumanOwnerOrFallback() uses DataList with filter: { type: 'human' }, limit: 1, which can return an anonymous browser user (uniqueId starting with anon-) or an arbitrary human depending on DB ordering. This can defeat the goal of attributing to the seeded owner.

Suggestion: mirror SessionDaemonServer.findSeededHumanOwner() logic by fetching multiple human users and selecting the first non-anonymous one (uniqueId not starting with anon-), ideally with a deterministic orderBy (e.g., createdAt asc/desc) to avoid nondeterministic attribution.

Suggested change
limit: 1,
context: params.context,
sessionId: params.sessionId,
});
if (result.success && result.items && result.items.length > 0) {
const owner = result.items[0];
return { id: owner.id, entity: owner };
}
// No human owner seeded yet — fall back to session userId
// Fetch multiple humans with deterministic ordering so we can skip anonymous users
limit: 50,
orderBy: { createdAt: 'asc' },
context: params.context,
sessionId: params.sessionId,
});
if (result.success && result.items && result.items.length > 0) {
// Prefer the first non-anonymous human (uniqueId not starting with "anon-")
for (const user of result.items) {
const uniqueId = (user as any).uniqueId as string | undefined;
if (!uniqueId || !uniqueId.startsWith('anon-')) {
return { id: user.id, entity: user };
}
}
}
// No suitable human owner found — fall back to session userId

Copilot uses AI. Check for mistakes.
Comment on lines 709 to +724
case 'cli': {
// CLI identity: Use uniqueId (@cli or env user)
const cliUniqueId = identity?.uniqueId || '@cli';
this.log.info(`💻 CLI session: resolving uniqueId=${cliUniqueId}`);

const existingCli = await this.findUserByUniqueId(cliUniqueId);
if (existingCli) {
user = existingCli;
// CLI = the human owner, same as browser. Single-owner system:
// ./jtag commands are Joel, not a separate "@cli" user.
const seededOwner = await this.findSeededHumanOwner();
if (seededOwner) {
user = seededOwner;
this.log.info(`✅ CLI session → seeded owner: ${user.displayName}`);
} else {
user = await this.createUser(params);
// No seeded owner yet (pre-seed or first boot) — fall back to uniqueId
const cliUniqueId = identity?.uniqueId || '@cli';
this.log.info(`💻 CLI session: no seeded owner, resolving uniqueId=${cliUniqueId}`);
const existingCli = await this.findUserByUniqueId(cliUniqueId);
if (existingCli) {
user = existingCli;
} else {
user = await this.createUser(params);
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cli clientType is now forcibly resolved to the seeded human owner when one exists. If the CLI/websocket client is also used for automation or multi-user testing, this collapses distinct identities into one and can change authorization/audit behavior.

Suggestion: gate this behavior behind an explicit “single-owner dev mode” config/flag, or restrict it to the specific placeholder uniqueId (e.g., '@cli') so other CLI identities can still resolve via uniqueId/userId as before.

Copilot uses AI. Check for mistakes.
joelteply added a commit that referenced this pull request Mar 25, 2026
#376 (chat/send userId): DONE — PR #387
#335 (duplicate tabs): DONE — PR #387
#317 (live double-init): DONE — PR #388
#360 (ORM dates): investigated, downgraded to Phase 1 — dates work
correctly, indexes working for high-traffic tables, cursor pagination
deferred to post-alpha.
joelteply pushed a commit that referenced this pull request May 1, 2026
…egression + #978 nullish-coalescing cleanup

THREE related changes from a live `npm start` test session 2026-05-01:

1. ALPHA-GAP-ANALYSIS.md is now THE single source of truth
   - Refreshed to 2026-05-01 with live-verified state
   - New "Today's Snapshot" section: what worked + broke in real
     `npm start` from feat/airc-send-command (#977 + #978 + #979 stack)
   - 3 new live-observed bugs in Phase 0:
     · NEW-A: continuum-core-server SIGABRT in vendored llama.cpp
       Metal `llm_build_smallthinker` cleanup. Real stack captured.
     · NEW-B: seed retries 21x/480s before giving up (concrete
       fail-fast fix designed)
     · NEW-C: shared/config.ts has /Users/joelteply/... HARDCODED
       (Carl-blocker)
   - 10 closed-since-Apr-17 items marked DONE
   - 21 new high-numbered open issues catalogued
   - Shortest path to "Install. Talk to AI." spelled out
   - Open PRs (continuum #976 #977 #978 #979 + airc #387) listed
   - Workflow note per Joel 2026-05-01: merge-to-canary, not PR-and-wait
   - Two predecessor docs DELETED + content folded:
     · docs/PRE-ALPHA-GAP-ANALYSIS.md (predates DMR pivot)
     · docs/planning/CARL-AND-DEV-PATH-TO-WORKING.md (interim)

2. SystemMilestones.ts — fix the #977 regression
   Original #977 added CORE_READY as SERVER_READY dep; consequence
   was browser never opens when Rust core SIGABRTs (Joel observed:
   "I don't see a browser"). This commit decouples them — SERVER_READY
   depends only on SERVER_START. SYSTEM_HEALTHY (monitoring signal)
   still requires both. Live-verified: browser opens despite
   SIGABRT-looping core. Joel confirmed: "opened good job."

3. AiLocalInference{Start,Status}ServerCommand.ts — || → ??
   Three nullish-coalescing fixes left uncommitted from PR #978.

NEXT STEPS for the test devices Joel just mentioned:
1. Verify NEW-C path bug repros on fresh test device (it should)
2. File NEW-A + NEW-C as GitHub issues
3. Trace seed-time llm_build_smallthinker call chain — likely a
   Candle-on-chat-hot-path bug per PR891 pivot
4. Implement seed fail-fast (~30 LOC) so install UX doesn't rot 8
   minutes per attempt

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
joelteply pushed a commit to RebelTechPro/continuum that referenced this pull request May 13, 2026
…irst backbone for Claude Code / Codex / openclaws / Hermes

Captures Joel's strategic framing live during the 2026-04-30 AI capacity
squeeze (Codex auto-downgraded to mini, paid Anthropic users hitting
rate limits, public AI stocks correcting on demand-outpaces-supply).

Architecture (3 layers):
  L1: External agent (Claude Code, Codex, openclaws, Hermes, ...)
      Pointed at local Continuum via ANTHROPIC_BASE_URL / OPENAI_BASE_URL.
      No code changes required to the external agent.
  L2: Continuum local truth (Rust core)
      anthropic_compat.rs (already exists) + openai_compat.rs (to add)
      sit in front of the same AIAdapter trait. CandleAdapter +
      LlamaCppAdapter + MLX backend already implement it.
      LocalClaudeCodeProvider.ts already does the proof-of-concept
      end-to-end (start server + ANTHROPIC_BASE_URL + spawn Claude Code).
  L3: airc capability mesh (multi-machine multiplier)
      Peers publish loaded models + free VRAM + endpoints over a
      dedicated #ai-capability airc channel. Layer 2 routers consult
      the peer table + route requests to the best-fit peer. Inference
      traffic itself goes peer-to-peer via Tailscale or LAN.

Native-truth + thin-SDK rule applied (per Joel's CLAUDE.md): Rust core
is truth, TS daemon is the SDK, external agents are outermost SDKs that
consume via standard HTTP. No layer reimplements another's truth.

PC-paradigm framing: small / nimble / collaborative / scaling /
distributed across all our hardware. Ship pretty-well-first, then build
to dominance. The PC didn't beat the mainframe by being faster on day
one — it beat it by being everywhere, owned individually, no central
permission to compute.

Training flywheel as the moat:
  - LocalClaudeCodeProvider already has captureTraining=true
  - TrainingDataAccumulator already routes to academy pipeline
  - forge-alloy already builds LoRAs from captured interactions
  - Cloud APIs literally cannot train per-user on private data without
    crossing publicly-committed lines. We can — locally, opt-in,
    transparently. That's the differentiator.

Phased delivery plan:
  Phase 0 (this week, in flight): airc#381 layer A (PR CambrianTech#387) + B (CambrianTech#385
    merged), airc#383 (PR CambrianTech#384), continuum CambrianTech#722/CambrianTech#56/CambrianTech#75 stabilization
  Phase 1 (1-2 weeks): single-machine local fallback for Codex via
    OPENAI_BASE_URL + rate-limit-detect middleware
  Phase 2 (1 week): airc capability channel + peer announcements
  Phase 3 (2-3 weeks): multi-peer routing across the household grid
  Phase 4: UX polish + training-flywheel generalization

Document includes:
  - Full bug + Rust-enhancement triage (CambrianTech#722, CambrianTech#56, CambrianTech#75, CambrianTech#71, CambrianTech#73, CambrianTech#39,
    CambrianTech#765, CambrianTech#582, CambrianTech#860, CambrianTech#770, CambrianTech#637, CambrianTech#908) with how each blocks or
    composes with the integration
  - Cross-references to existing arch docs (PERSONA-COGNITION-RUST-
    MIGRATION, PERSONA-CONTEXT-PAGING, RECIPE-EXECUTION-RUNTIME,
    RESOURCE-ARCHITECTURE, MLX-BACKEND, FORGE-ALLOY-SPEC)
  - Open questions (license/ToS, capability staleness, auth shim,
    cost accounting, model coherence across peers)
  - Out-of-scope clarifications (training across peers, single-request
    distributed inference, replacing Continuum web UI)
  - Action items for the mesh — concrete first claims for each peer

Why we wrote this NOW: the capacity squeeze tipping users toward local
is also tipping AI peers (us) toward "we won't be able to design
tomorrow." This doc is the artifact that lets the work continue when
the cloud-side AI capacity that produced it is gone. Read this first;
the substrate it describes is buildable from surfaces already in
workers/continuum-core/, src/system/sentinel/coding-agents/,
src/daemons/ai-provider-daemon/, and the airc mesh. None of it is
hypothetical.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chat/send userId bug — personas can't post to chat rooms Multiple browser tabs launched on npm start

2 participants