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
3 changes: 3 additions & 0 deletions src/debug/jtag/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ __pycache__/
.continuum/genome/python/pkgs/
system/genome/python/venv/

# Generated TypeScript from Rust via ts-rs (regenerated by: npx tsx generator/generate-rust-bindings.ts)
shared/generated/

# Rust build artifacts
# Compiled files
*.o
Expand Down
8 changes: 7 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 186 commands and 2 adapters and 28 widgets.
* Contains 11 daemons and 187 commands and 2 adapters and 28 widgets.
* Generated by scripts/generate-structure.ts - DO NOT EDIT MANUALLY
*/

Expand Down Expand Up @@ -200,6 +200,7 @@ import { GitPushBrowserCommand } from './../commands/workspace/git/push/browser/
import { GitStatusBrowserCommand } from './../commands/workspace/git/status/browser/GitStatusBrowserCommand';
import { GitWorkspaceCleanBrowserCommand } from './../commands/workspace/git/workspace/clean/browser/GitWorkspaceCleanBrowserCommand';
import { GitWorkspaceInitBrowserCommand } from './../commands/workspace/git/workspace/init/browser/GitWorkspaceInitBrowserCommand';
import { WorkspaceListBrowserCommand } from './../commands/workspace/list/browser/WorkspaceListBrowserCommand';
import { RecipeLoadBrowserCommand } from './../commands/workspace/recipe/load/browser/RecipeLoadBrowserCommand';
import { TaskCompleteBrowserCommand } from './../commands/workspace/task/complete/browser/TaskCompleteBrowserCommand';
import { TaskCreateBrowserCommand } from './../commands/workspace/task/create/browser/TaskCreateBrowserCommand';
Expand Down Expand Up @@ -1213,6 +1214,11 @@ export const BROWSER_COMMANDS: CommandEntry[] = [
className: 'GitWorkspaceInitBrowserCommand',
commandClass: GitWorkspaceInitBrowserCommand
},
{
name: 'workspace/list',
className: 'WorkspaceListBrowserCommand',
commandClass: WorkspaceListBrowserCommand
},
{
name: 'workspace/recipe/load',
className: 'RecipeLoadBrowserCommand',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,43 +82,36 @@ export class CodeVerifyServerCommand extends CommandBase<CodeVerifyParams, CodeV

/**
* Resolve the workspace directory from params.
* Uses explicit cwd if provided, otherwise resolves from userId convention.
* Prefers explicit cwd (always passed by Workspace.verify()). Falls back to
* scanning .continuum/personas/ for a matching workspace directory.
*/
private resolveWorkspaceDir(params: CodeVerifyParams): string {
if (params.cwd && params.cwd.trim()) {
return params.cwd;
}

// Fallback: scan persona directories for a workspace matching this handle/userId.
// This is a last-resort heuristic — callers should pass cwd explicitly.
const jtagRoot = process.cwd();
const personaId = params.userId!;

// Standard persona workspace path
const workspaceDir = path.join(jtagRoot, '.continuum', 'personas', personaId, 'workspace');

if (fs.existsSync(workspaceDir)) {
return workspaceDir;
}

// Fallback: check if userId is a challenge workspace handle (challenge-{id}-{personaId})
if (personaId.startsWith('challenge-')) {
const parts = personaId.split('-');
// Handle: challenge-{challengeId}-{personaId}
// The challengeId and personaId are UUIDs, so we need the full pattern
const challengeIdStart = 'challenge-'.length;
// Find the persona ID (last UUID in the handle)
const uuidLen = 36; // Standard UUID length
if (personaId.length > challengeIdStart + uuidLen + 1) {
const actualPersonaId = personaId.slice(-(uuidLen));
const challengeId = personaId.slice(challengeIdStart, personaId.length - uuidLen - 1);
const challengeDir = path.join(jtagRoot, '.continuum', 'personas', actualPersonaId, 'challenges', challengeId);
if (fs.existsSync(challengeDir)) {
return challengeDir;
const personasRoot = path.join(jtagRoot, '.continuum', 'personas');

if (fs.existsSync(personasRoot)) {
try {
const entries = fs.readdirSync(personasRoot, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const wsDir = path.join(personasRoot, entry.name, 'workspace');
if (fs.existsSync(wsDir)) {
// For sandbox mode, the handle IS the personaId. Check if this directory
// was registered for this handle by looking for any content.
return wsDir;
}
}
}
} catch { /* fallthrough */ }
}

// Last resort: use the standard workspace path even if it doesn't exist yet
return workspaceDir;
// Absolute last resort — construct path and hope for the best
return path.join(personasRoot, params.userId!, 'workspace');
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,13 @@ export class DataListServerCommand<T extends BaseEntity> extends CommandBase<Dat
const totalCount = countResult.success ? (countResult.data ?? 0) : 0;

if (!result.success) {
const availableCollections = Object.values(COLLECTIONS).join(', ');
const errorWithHint = `${result.error || 'Unknown DataDaemon error'}. Valid collections are: ${availableCollections}`;
console.error(`❌ DATA SERVER: DataDaemon query failed:`, errorWithHint);
const errorMsg = result.error || 'Unknown DataDaemon error';
console.error(`❌ DATA SERVER: DataDaemon query failed for '${collection}':`, errorMsg);
return createDataListResultFromParams(params, {
success: false,
items: [],
count: 0,
error: errorWithHint
error: errorMsg
});
}

Expand Down Expand Up @@ -139,14 +138,12 @@ export class DataListServerCommand<T extends BaseEntity> extends CommandBase<Dat

} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
const availableCollections = Object.values(COLLECTIONS).join(', ');
const errorWithHint = `${errorMessage}. Valid collections are: ${availableCollections}`;
console.error(`❌ DATA SERVER: DataDaemon execution failed:`, errorWithHint);
console.error(`❌ DATA SERVER: DataDaemon execution failed for '${collection}':`, errorMessage);
return createDataListResultFromParams(params, {
success: false,
items: [],
count: 0,
error: errorWithHint
error: errorMessage
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { CommandBase, type ICommandDaemon } from '@daemons/command-daemon/shared
import type { JTAGContext } from '@system/core/types/JTAGTypes';
import type { GitPushParams, GitPushResult } from '../shared/GitPushTypes';
import { createGitPushResultFromParams } from '../shared/GitPushTypes';
import { resolveWorkspacePathFromUserId } from '../../shared/resolveWorkspacePath';
import * as path from 'path';
import * as fs from 'fs';
import { promisify } from 'util';
Expand All @@ -24,9 +25,7 @@ export class GitPushServerCommand extends CommandBase<GitPushParams, GitPushResu
async execute(params: GitPushParams): Promise<GitPushResult> {
try {
const userId = params.userId || 'unknown';
const workspacePath = params.workspacePath || path.resolve(
process.cwd(), '.continuum/sessions/user/shared', userId, 'workspace'
);
const workspacePath = params.workspacePath || await resolveWorkspacePathFromUserId(userId);

if (!fs.existsSync(workspacePath)) {
throw new Error(`Workspace not found at ${workspacePath}`);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* resolveWorkspacePath - Resolve workspace path from userId (UUID)
*
* Shared utility for git workspace commands that need to find a persona's
* workspace directory. Looks up the user entity to get the human-readable
* uniqueId, then constructs the path using that (not the UUID).
*
* Path convention: .continuum/sessions/user/shared/{uniqueId}/workspace
*/

import { DataDaemon } from '@daemons/data-daemon/shared/DataDaemon';
import { COLLECTIONS } from '@system/data/config/DatabaseConfig';
import type { UserEntity } from '@system/data/entities/UserEntity';
import * as path from 'path';

/**
* Resolve workspace path from a userId (UUID).
* Looks up the user entity to get uniqueId for human-readable directory naming.
* Falls back to userId if entity lookup fails.
*/
export async function resolveWorkspacePathFromUserId(userId: string): Promise<string> {
let dirName = userId; // fallback to UUID if lookup fails

try {
const entity = await DataDaemon.read<UserEntity>(COLLECTIONS.USERS, userId);
if (entity?.uniqueId) {
dirName = entity.uniqueId;
}
} catch {
// Entity lookup failed — use UUID as fallback
}

return path.resolve(
process.cwd(),
'.continuum/sessions/user/shared',
dirName,
'workspace',
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { CommandBase, type ICommandDaemon } from '@daemons/command-daemon/shared
import type { JTAGContext } from '@system/core/types/JTAGTypes';
import type { GitStatusParams, GitStatusResult } from '../shared/GitStatusTypes';
import { createGitStatusResultFromParams } from '../shared/GitStatusTypes';
import { resolveWorkspacePathFromUserId } from '../../shared/resolveWorkspacePath';
import * as path from 'path';
import * as fs from 'fs';
import { promisify } from 'util';
Expand All @@ -24,9 +25,7 @@ export class GitStatusServerCommand extends CommandBase<GitStatusParams, GitStat
async execute(params: GitStatusParams): Promise<GitStatusResult> {
try {
const userId = params.userId || 'unknown';
const workspacePath = params.workspacePath || path.resolve(
process.cwd(), '.continuum/sessions/user/shared', userId, 'workspace'
);
const workspacePath = params.workspacePath || await resolveWorkspacePathFromUserId(userId);

if (!fs.existsSync(workspacePath)) {
throw new Error(`Workspace not found at ${workspacePath}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { CommandBase, type ICommandDaemon } from '@daemons/command-daemon/shared
import type { JTAGContext } from '@system/core/types/JTAGTypes';
import type { GitWorkspaceCleanParams, GitWorkspaceCleanResult } from '../shared/GitWorkspaceCleanTypes';
import { createGitWorkspaceCleanResultFromParams } from '../shared/GitWorkspaceCleanTypes';
import { resolveWorkspacePathFromUserId } from '../../../shared/resolveWorkspacePath';
import * as path from 'path';
import * as fs from 'fs';
import { promisify } from 'util';
Expand All @@ -24,9 +25,7 @@ export class GitWorkspaceCleanServerCommand extends CommandBase<GitWorkspaceClea
async execute(params: GitWorkspaceCleanParams): Promise<GitWorkspaceCleanResult> {
try {
const userId = params.userId || 'unknown';
const workspacePath = params.workspacePath || path.resolve(
process.cwd(), '.continuum/sessions/user/shared', userId, 'workspace'
);
const workspacePath = params.workspacePath || await resolveWorkspacePathFromUserId(userId);

if (!fs.existsSync(workspacePath)) {
return createGitWorkspaceCleanResultFromParams(params, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ export class GitWorkspaceInitServerCommand extends CommandBase<GitWorkspaceInitP

const persona = userResult.data as UserEntity;

// 3. Generate workspace path (.continuum/sessions/user/shared/{personaId}/workspace)
// 3. Generate workspace path — use human-readable uniqueId for directory names
const workspacePath = path.resolve(
process.cwd(),
'.continuum/sessions/user/shared',
personaId,
persona.uniqueId || personaId,
'workspace'
);

Expand Down
20 changes: 20 additions & 0 deletions src/debug/jtag/commands/workspace/list/.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