Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,92 @@ const result = await this.executeCommand<DataListResult<UserEntity>>('data/list'

---

## 🦀 RUST → TYPESCRIPT TYPE BOUNDARIES (ts-rs)

**Single source of truth: Rust defines wire types, ts-rs generates TypeScript. NEVER hand-write duplicate types.**

### How It Works

1. **Rust struct** with `#[derive(TS)]` defines the canonical type
2. **ts-rs macro** generates TypeScript `export type` at compile time
3. **TypeScript** imports from `shared/generated/` — no manual duplication
4. **Serde** handles JSON serialization on both sides

### Pattern

```rust
// Rust (source of truth)
use ts_rs::TS;

#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../../../shared/generated/code/WriteResult.ts")]
pub struct WriteResult {
pub success: bool,
#[ts(optional)]
pub change_id: Option<String>,
pub file_path: String,
#[ts(type = "number")] // u64 → number (not bigint)
pub bytes_written: u64,
#[ts(optional)]
pub error: Option<String>,
}
```

```typescript
// TypeScript (generated — DO NOT EDIT)
export type WriteResult = { success: boolean, change_id?: string, file_path: string, bytes_written: number, error?: string };

// Consuming code imports from generated barrel
import type { WriteResult, ReadResult, EditMode } from '@shared/generated/code';
```

### ts-rs Attribute Reference

| Attribute | Purpose | Example |
|-----------|---------|---------|
| `#[ts(export)]` | Mark for TS generation | `#[derive(TS)] #[ts(export)]` |
| `#[ts(export_to = "path")]` | Output file path (relative to `bindings/`) | `"../../../shared/generated/code/X.ts"` |
| `#[ts(type = "string")]` | Override TS type for field | Uuid → string |
| `#[ts(type = "number")]` | Override TS type for field | u64 → number |
| `#[ts(optional)]` | Mark as optional in TS | Option<T> → `field?: T` |
| `#[ts(type = "Array<string>")]` | Complex type mapping | Vec<Uuid> → Array<string> |

### Regenerating Bindings

```bash
cargo test --package continuum-core --lib # Generates all *.ts in shared/generated/
```

### Generated Output Structure

```
shared/generated/
├── index.ts # Barrel export (re-exports all modules)
├── code/ # Code module (file ops, change graph, search, tree)
│ ├── index.ts
│ ├── ChangeNode.ts, EditMode.ts, WriteResult.ts, ReadResult.ts, ...
├── persona/ # Persona cognition (state, inbox, channels)
│ ├── index.ts
│ ├── PersonaState.ts, InboxMessage.ts, CognitionDecision.ts, ...
├── rag/ # RAG pipeline (context, messages, options)
│ ├── index.ts
│ ├── RagContext.ts, LlmMessage.ts, ...
└── ipc/ # IPC protocol types
├── index.ts
└── InboxMessageRequest.ts
```

### Rules (Non-Negotiable)

1. **NEVER hand-write types that cross the Rust↔TS boundary** — add `#[derive(TS)]` to the Rust struct
2. **NEVER use `object`, `any`, `unknown`, or `Record<string, unknown>`** for Rust wire types — import the generated type
3. **IDs are `UUID`** (from `CrossPlatformUUID`) — never plain `string` for identity fields
4. **Use `CommandParams.userId`** for caller identity — it's already on the base type, auto-injected by infrastructure
5. **Barrel exports** — every generated module has an `index.ts`; import from the barrel, not individual files
6. **Regenerate after Rust changes** — `cargo test` triggers ts-rs macro; commit both Rust and generated TS

---

## 📁 PATH ALIASES (New! Use These Going Forward)

**TypeScript path aliases are now configured** to eliminate relative import hell (`../../../../`).
Expand Down
31 changes: 31 additions & 0 deletions src/debug/jtag/api/data-seed/RoomDataSeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,37 @@ export class RoomDataSeed {
newsroom.tags = ['news', 'current-events', 'awareness'];
rooms.push(newsroom);

// Code room - collaborative software development
const code = new RoomEntity();
code.uniqueId = ROOM_UNIQUE_IDS.CODE;
code.name = 'code';
code.displayName = 'Code';
code.description = 'Collaborative coding — reading, writing, reviewing, and shipping code as a team';
code.topic = 'Software development with real tools and real agent loops';
code.type = 'public';
code.status = 'active';
code.ownerId = humanUserId;
code.lastMessageAt = now;
code.recipeId = 'coding';
code.privacy = {
isPublic: true,
requiresInvite: false,
allowGuestAccess: false,
searchable: true
};
code.settings = {
allowThreads: true,
allowReactions: true,
allowFileSharing: true,
messageRetentionDays: 365,
slowMode: 0
};
code.members = [
{ userId: humanUserId, role: 'owner', joinedAt: now }
];
code.tags = ['coding', 'development', 'engineering'];
rooms.push(code);

return {
rooms: rooms as readonly RoomEntity[],
totalCount: rooms.length,
Expand Down
104 changes: 103 additions & 1 deletion src/debug/jtag/browser/generated.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Browser Structure Registry - Auto-generated
*
* Contains 11 daemons and 166 commands and 2 adapters and 27 widgets.
* Contains 11 daemons and 183 commands and 2 adapters and 27 widgets.
* Generated by scripts/generate-structure.ts - DO NOT EDIT MANUALLY
*/

Expand Down Expand Up @@ -43,6 +43,18 @@ import { AIValidateResponseBrowserCommand } from './../commands/ai/validate-resp
import { CanvasStrokeAddBrowserCommand } from './../commands/canvas/stroke/add/browser/CanvasStrokeAddBrowserCommand';
import { CanvasStrokeListBrowserCommand } from './../commands/canvas/stroke/list/browser/CanvasStrokeListBrowserCommand';
import { CanvasVisionBrowserCommand } from './../commands/canvas/vision/browser/CanvasVisionBrowserCommand';
import { CodeDiffBrowserCommand } from './../commands/code/diff/browser/CodeDiffBrowserCommand';
import { CodeEditBrowserCommand } from './../commands/code/edit/browser/CodeEditBrowserCommand';
import { CodeGitBrowserCommand } from './../commands/code/git/browser/CodeGitBrowserCommand';
import { CodeHistoryBrowserCommand } from './../commands/code/history/browser/CodeHistoryBrowserCommand';
import { CodeReadBrowserCommand } from './../commands/code/read/browser/CodeReadBrowserCommand';
import { CodeSearchBrowserCommand } from './../commands/code/search/browser/CodeSearchBrowserCommand';
import { CodeShellSentinelBrowserCommand } from './../commands/code/shell/sentinel/browser/CodeShellSentinelBrowserCommand';
import { CodeShellWatchBrowserCommand } from './../commands/code/shell/watch/browser/CodeShellWatchBrowserCommand';
import { CodeTreeBrowserCommand } from './../commands/code/tree/browser/CodeTreeBrowserCommand';
import { CodeUndoBrowserCommand } from './../commands/code/undo/browser/CodeUndoBrowserCommand';
import { CodeVerifyBrowserCommand } from './../commands/code/verify/browser/CodeVerifyBrowserCommand';
import { CodeWriteBrowserCommand } from './../commands/code/write/browser/CodeWriteBrowserCommand';
import { ActivityUserPresentCommand } from './../commands/collaboration/activity/user-present/browser/ActivityUserPresentCommand';
import { ChatAnalyzeBrowserCommand } from './../commands/collaboration/chat/analyze/browser/ChatAnalyzeBrowserCommand';
import { ChatExportBrowserCommand } from './../commands/collaboration/chat/export/browser/ChatExportBrowserCommand';
Expand Down Expand Up @@ -141,6 +153,11 @@ import { SessionCreateBrowserCommand } from './../commands/session/create/browse
import { SessionDestroyBrowserCommand } from './../commands/session/destroy/browser/SessionDestroyBrowserCommand';
import { SessionGetIdBrowserCommand } from './../commands/session/get-id/browser/SessionGetIdBrowserCommand';
import { SessionGetUserBrowserCommand } from './../commands/session/get-user/browser/SessionGetUserBrowserCommand';
import { SkillActivateBrowserCommand } from './../commands/skill/activate/browser/SkillActivateBrowserCommand';
import { SkillGenerateBrowserCommand } from './../commands/skill/generate/browser/SkillGenerateBrowserCommand';
import { SkillListBrowserCommand } from './../commands/skill/list/browser/SkillListBrowserCommand';
import { SkillProposeBrowserCommand } from './../commands/skill/propose/browser/SkillProposeBrowserCommand';
import { SkillValidateBrowserCommand } from './../commands/skill/validate/browser/SkillValidateBrowserCommand';
import { SocialBrowseBrowserCommand } from './../commands/social/browse/browser/SocialBrowseBrowserCommand';
import { SocialClassifyBrowserCommand } from './../commands/social/classify/browser/SocialClassifyBrowserCommand';
import { SocialCommentBrowserCommand } from './../commands/social/comment/browser/SocialCommentBrowserCommand';
Expand Down Expand Up @@ -407,6 +424,66 @@ export const BROWSER_COMMANDS: CommandEntry[] = [
className: 'CanvasVisionBrowserCommand',
commandClass: CanvasVisionBrowserCommand
},
{
name: 'code/diff',
className: 'CodeDiffBrowserCommand',
commandClass: CodeDiffBrowserCommand
},
{
name: 'code/edit',
className: 'CodeEditBrowserCommand',
commandClass: CodeEditBrowserCommand
},
{
name: 'code/git',
className: 'CodeGitBrowserCommand',
commandClass: CodeGitBrowserCommand
},
{
name: 'code/history',
className: 'CodeHistoryBrowserCommand',
commandClass: CodeHistoryBrowserCommand
},
{
name: 'code/read',
className: 'CodeReadBrowserCommand',
commandClass: CodeReadBrowserCommand
},
{
name: 'code/search',
className: 'CodeSearchBrowserCommand',
commandClass: CodeSearchBrowserCommand
},
{
name: 'code/shell/sentinel',
className: 'CodeShellSentinelBrowserCommand',
commandClass: CodeShellSentinelBrowserCommand
},
{
name: 'code/shell/watch',
className: 'CodeShellWatchBrowserCommand',
commandClass: CodeShellWatchBrowserCommand
},
{
name: 'code/tree',
className: 'CodeTreeBrowserCommand',
commandClass: CodeTreeBrowserCommand
},
{
name: 'code/undo',
className: 'CodeUndoBrowserCommand',
commandClass: CodeUndoBrowserCommand
},
{
name: 'code/verify',
className: 'CodeVerifyBrowserCommand',
commandClass: CodeVerifyBrowserCommand
},
{
name: 'code/write',
className: 'CodeWriteBrowserCommand',
commandClass: CodeWriteBrowserCommand
},
{
name: 'collaboration/activity/user-present',
className: 'ActivityUserPresentCommand',
Expand Down Expand Up @@ -897,6 +974,31 @@ export const BROWSER_COMMANDS: CommandEntry[] = [
className: 'SessionGetUserBrowserCommand',
commandClass: SessionGetUserBrowserCommand
},
{
name: 'skill/activate',
className: 'SkillActivateBrowserCommand',
commandClass: SkillActivateBrowserCommand
},
{
name: 'skill/generate',
className: 'SkillGenerateBrowserCommand',
commandClass: SkillGenerateBrowserCommand
},
{
name: 'skill/list',
className: 'SkillListBrowserCommand',
commandClass: SkillListBrowserCommand
},
{
name: 'skill/propose',
className: 'SkillProposeBrowserCommand',
commandClass: SkillProposeBrowserCommand
},
{
name: 'skill/validate',
className: 'SkillValidateBrowserCommand',
commandClass: SkillValidateBrowserCommand
},
{
name: 'social/browse',
className: 'SocialBrowseBrowserCommand',
Expand Down
12 changes: 7 additions & 5 deletions src/debug/jtag/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { COMMANDS } from './shared/generated-command-constants';
import { DATA_COMMANDS } from './commands/data/shared/DataCommandConstants';
import { FILE_COMMANDS } from './commands/file/shared/FileCommandConstants';
import { USER_COMMANDS } from './commands/shared/SystemCommandConstants';
import { CODE_COMMANDS } from './commands/development/code/shared/CodeCommandConstants';
import * as fs from 'fs';
import * as path from 'path';

Expand Down Expand Up @@ -226,8 +225,8 @@ async function main() {
// Map of commands to their primary parameter name
const singleParamCommands: Record<string, string> = {
'help': 'commandName',
[CODE_COMMANDS.READ]: 'path',
[CODE_COMMANDS.FIND]: 'pattern',
'code/read': 'path',
'code/search': 'pattern',
[FILE_COMMANDS.LOAD]: 'path',
[FILE_COMMANDS.SAVE]: 'path',
[DATA_COMMANDS.READ]: 'id',
Expand Down Expand Up @@ -389,8 +388,11 @@ async function main() {
const isInferenceCommand = command.startsWith('inference/');
const isSocialCommand = command.startsWith('social/');
const isCollaborationCommand = command.startsWith('collaboration/');
const needsLongerTimeout = isAICommand || isInferenceCommand || isSocialCommand || isInterfaceCommand || isCollaborationCommand;
const timeoutMs = isGenomeCommand ? 300000 : needsLongerTimeout ? 60000 : 10000; // 5min for genome, 60s for AI/inference/social/interface/collaboration, 10s for others
const isChallengeCommand = command.startsWith('challenge/');
const isCodeCommand = command.startsWith('code/');
const needsLongerTimeout = isAICommand || isInferenceCommand || isSocialCommand || isInterfaceCommand || isCollaborationCommand || isCodeCommand;
const needsLongTimeout = isGenomeCommand || isChallengeCommand;
const timeoutMs = needsLongTimeout ? 300000 : needsLongerTimeout ? 60000 : 10000; // 5min for genome/challenge, 60s for AI/inference/social/interface/collaboration/code, 10s for others
const timeoutSeconds = timeoutMs / 1000;

const commandTimeout = new Promise((_, reject) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,8 @@ export class RAGInspectServerCommand extends RAGInspectCommand {
if (params.triggerMessageId) {
try {
// Load the trigger message
const msgResult = await DataDaemon.read<ChatMessageEntity>(ChatMessageEntity.collection, params.triggerMessageId);
if (msgResult.success && msgResult.data) {
const msg = msgResult.data.data;
const msg = await DataDaemon.read<ChatMessageEntity>(ChatMessageEntity.collection, params.triggerMessageId);
if (msg) {

// Get actual decision from ThoughtStream
const coordinator = getThoughtStreamCoordinator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ export class ShouldRespondFastServerCommand extends ShouldRespondFastCommand {
});
}

if (!params.messageText) {
return this.buildResult(params, false, 0, {
reasoning: 'Missing required parameter: messageText'
});
}

// Default contextId to a placeholder if not provided (allows tool to work)
const contextId = params.contextId ?? 'default-context';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,14 @@ export class ThoughtStreamServerCommand extends ThoughtStreamCommand {

try {
// Query data daemon for the message
const result = await DataDaemon.read<ChatMessageEntity>(
const msg = await DataDaemon.read<ChatMessageEntity>(
COLLECTIONS.CHAT_MESSAGES,
stream.messageId
);

if (result.success && result.data) {
const msg = result.data as any;
// Try different possible structures for message data
messageSender = msg.senderName || msg.data?.senderName || 'Unknown';
messageContent = msg.content?.text || msg.data?.content?.text || msg.text || '';
if (msg) {
messageSender = msg.senderName || 'Unknown';
messageContent = msg.content?.text ?? '';
}
} catch (error) {
console.warn(`⚠️ Could not load message ${stream.messageId}:`, error);
Expand Down Expand Up @@ -585,14 +583,13 @@ export class ThoughtStreamServerCommand extends ThoughtStreamCommand {

private async getPersonaName(personaId: string, params: ThoughtStreamParams): Promise<string> {
try {
const result = await DataDaemon.read<UserEntity>(
const user = await DataDaemon.read<UserEntity>(
COLLECTIONS.USERS,
personaId
);

if (result.success && result.data) {
const userData = result.data as any;
return userData.displayName || userData.name || personaId.slice(0, 8);
if (user) {
return user.displayName || personaId.slice(0, 8);
}
return personaId.slice(0, 8);
} catch {
Expand Down
20 changes: 20 additions & 0 deletions src/debug/jtag/commands/code/diff/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Development files
.eslintrc*
tsconfig*.json
vitest.config.ts

# Build artifacts
*.js.map
*.d.ts.map

# IDE
.vscode/
.idea/

# Logs
*.log
npm-debug.log*

# OS files
.DS_Store
Thumbs.db
Loading
Loading