Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import { Commands } from '@system/core/shared/Commands';
import type { VectorSearchParams, VectorSearchResult_CLI } from '@commands/data/vector-search/shared/VectorSearchCommandTypes';

import { VectorSearch } from '../../../../data/vector-search/shared/VectorSearchCommandTypes';
import { isPerPersonaCollection } from '@daemons/data-daemon/shared/ORMConfig';
import { CognitionLogger } from '@system/user/server/modules/cognition/CognitionLogger';

// Default collections that typically have semantic content
const DEFAULT_COLLECTIONS: CollectionName[] = [
'chat_messages',
Expand Down Expand Up @@ -121,14 +124,29 @@ export class AiContextSearchServerCommand extends CommandBase<AiContextSearchPar
filter.createdAt = { $gte: options.since };
}

// Resolve dbHandle: per-persona collections need the persona's longterm.db handle
let dbHandle: string | undefined;
if (isPerPersonaCollection(collection)) {
if (!options.personaId) {
console.debug(`CONTEXT-SEARCH: Skipping per-persona collection '${collection}' — no personaId`);
return [];
}
dbHandle = CognitionLogger.getDbHandle(options.personaId);
if (!dbHandle) {
console.debug(`CONTEXT-SEARCH: Skipping '${collection}' — persona ${options.personaId} DB not ready`);
return [];
}
}

// Use data/vector-search command (delegates to Rust embedding worker)
const result = await VectorSearch.execute({
collection,
queryText: query,
k: options.limit,
similarityThreshold: options.minSimilarity,
filter,
hybridMode: options.mode
hybridMode: options.mode,
...(dbHandle ? { dbHandle } : {})
});

if (!result.success || !result.results) {
Expand Down
3 changes: 2 additions & 1 deletion src/commands/data/create/server/DataCreateServerCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { createDataCreateResultFromParams } from '../shared/DataCreateTypes';
import { ORM } from '../../../../daemons/data-daemon/server/ORM';
import { BaseEntity } from '../../../../system/data/entities/BaseEntity';
import type { CollectionName } from '../../../../shared/generated-collection-constants';
import { resolveDbHandle } from '../../../../daemons/data-daemon/shared/ORMConfig';

export class DataCreateServerCommand extends DataCreateCommand {

Expand All @@ -32,7 +33,7 @@ export class DataCreateServerCommand extends DataCreateCommand {
collection as CollectionName,
params.data as BaseEntity,
params.suppressEvents ?? false,
params.dbHandle ?? 'default'
resolveDbHandle(collection, params.dbHandle)
);

return createDataCreateResultFromParams(params, {
Expand Down
5 changes: 3 additions & 2 deletions src/commands/data/delete/server/DataDeleteServerCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { createDataDeleteResultFromParams } from '../shared/DataDeleteTypes';
import { ORM } from '../../../../daemons/data-daemon/server/ORM';
import type { BaseEntity } from '@system/data/entities/BaseEntity';
import { isValidCollection, type CollectionName } from '../../../../shared/generated-collection-constants';
import { resolveDbHandle } from '../../../../daemons/data-daemon/shared/ORMConfig';

export class DataDeleteServerCommand extends CommandBase<DataDeleteParams, DataDeleteResult> {

Expand All @@ -35,7 +36,7 @@ export class DataDeleteServerCommand extends CommandBase<DataDeleteParams, DataD
// First read the entity before deletion for the event
let entityBeforeDelete: BaseEntity | null;
try {
entityBeforeDelete = await ORM.read(validCollection, params.id, params.dbHandle ?? 'default');
entityBeforeDelete = await ORM.read(validCollection, params.id, resolveDbHandle(collection, params.dbHandle));
} catch (error: any) {
return createDataDeleteResultFromParams(params, {
error: `Record not found: ${collection}/${params.id}`,
Expand All @@ -46,7 +47,7 @@ export class DataDeleteServerCommand extends CommandBase<DataDeleteParams, DataD
// DataDaemon throws on failure, returns success result on success
// Events are emitted by DataDaemon.remove() via universal Events system
// Pass suppressEvents flag to prevent events during internal operations (e.g., archiving)
const result = await ORM.remove(validCollection, params.id, params.suppressEvents, params.dbHandle ?? 'default');
const result = await ORM.remove(validCollection, params.id, params.suppressEvents, resolveDbHandle(collection, params.dbHandle));

if (!result.success) {
throw new Error(result.error ?? 'Unknown error during data delete');
Expand Down
4 changes: 3 additions & 1 deletion src/commands/data/list/server/DataListServerCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { BaseEntity } from '../../../../system/data/entities/BaseEntity';
import { ORM } from '../../../../daemons/data-daemon/server/ORM';
import { DataDaemon } from '../../../../daemons/data-daemon/shared/DataDaemon'; // Only for getDescriptionFieldForCollection
import { COLLECTIONS } from '../../../../system/data/config/DatabaseConfig';
import { resolveDbHandle } from '../../../../daemons/data-daemon/shared/ORMConfig';

// Rust-style config defaults for generic data access
const DEFAULT_CONFIG = {
Expand Down Expand Up @@ -82,7 +83,8 @@ export class DataListServerCommand<T extends BaseEntity> extends CommandBase<Dat
};

// Pass handle directly to ORM — ORM resolves handle → path internally
const handle = params.dbHandle ?? 'default';
// resolveDbHandle REJECTS per-persona collections without a handle (no silent fallback)
const handle = resolveDbHandle(collection, params.dbHandle);
if (!params.skipCount) {
countResult = await ORM.count(countQuery, handle);
}
Expand Down
3 changes: 2 additions & 1 deletion src/commands/data/read/server/DataReadServerCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { BaseEntity } from '../../../../system/data/entities/BaseEntity';
import type { MediaItem, ChatMessageEntity } from '../../../../system/data/entities/ChatMessageEntity';
import { DataReadCommand } from '../shared/DataReadCommand';
import { isValidCollection, type CollectionName, COLLECTIONS } from '../../../../shared/generated-collection-constants';
import { resolveDbHandle } from '../../../../daemons/data-daemon/shared/ORMConfig';

export class DataReadServerCommand extends DataReadCommand<BaseEntity> {

Expand Down Expand Up @@ -50,7 +51,7 @@ export class DataReadServerCommand extends DataReadCommand<BaseEntity> {

try {
// Use DataDaemon for consistent storage access
const entity = await ORM.read<BaseEntity>(validCollection, params.id, params.dbHandle ?? 'default');
const entity = await ORM.read<BaseEntity>(validCollection, params.id, resolveDbHandle(params.collection, params.dbHandle));

if (entity) {

Expand Down
2 changes: 1 addition & 1 deletion src/commands/data/shared/BaseDataTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const createBaseDataParams = (
userId: SYSTEM_SCOPES.SYSTEM,
...data,
backend: data.backend ?? 'server',
dbHandle: data.dbHandle ?? 'default'
dbHandle: data.dbHandle
});

/**
Expand Down
3 changes: 2 additions & 1 deletion src/commands/data/update/server/DataUpdateServerCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ORM } from '../../../../daemons/data-daemon/server/ORM';
import { BaseEntity } from '../../../../system/data/entities/BaseEntity';
import { DataUpdateCommand } from '../shared/DataUpdateCommand';
import type { CollectionName } from '../../../../shared/generated-collection-constants';
import { resolveDbHandle } from '../../../../daemons/data-daemon/shared/ORMConfig';

export class DataUpdateServerCommand extends DataUpdateCommand<BaseEntity> {

Expand All @@ -29,7 +30,7 @@ export class DataUpdateServerCommand extends DataUpdateCommand<BaseEntity> {
params.id,
params.data as Partial<BaseEntity>,
params.incrementVersion ?? true,
params.dbHandle ?? 'default',
resolveDbHandle(collection, params.dbHandle),
params.suppressEvents ?? false
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { createVectorSearchResultFromParams } from '../shared/VectorSearchComman
import { ORM } from '../../../../daemons/data-daemon/server/ORM';
import type { RecordData } from '../../../../daemons/data-daemon/shared/DataStorageAdapter';
import { DEFAULT_EMBEDDING_MODELS } from '../../../../daemons/data-daemon/shared/VectorSearchTypes';
import { resolveDbHandle } from '../../../../daemons/data-daemon/shared/ORMConfig';

const DEFAULT_CONFIG = {
vectorSearch: {
Expand Down Expand Up @@ -62,7 +63,7 @@ export class VectorSearchServerCommand extends CommandBase<VectorSearchParams, V
// ORM.vectorSearch resolves dbHandle to dbPath internally
const searchResult = await ORM.vectorSearch<RecordData>({
collection: params.collection,
dbHandle: params.dbHandle ?? 'default',
dbHandle: resolveDbHandle(params.collection, params.dbHandle),
queryText: params.queryText,
queryVector: params.queryVector,
k,
Expand Down
53 changes: 53 additions & 0 deletions src/daemons/data-daemon/shared/ORMConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,5 +279,58 @@ export function printBackendConfig(): void {
console.log('================================\n');
}

// ─── Per-Persona Collection Guard ──────────────────────────────────────────────
//
// These collections ONLY exist in per-persona databases (longterm.db).
// They NEVER exist in the main/shared database.
// Querying them with handle='default' is ALWAYS a bug — it means a caller
// forgot to pass the persona's dbHandle, and the query would hit the main DB
// where the table doesn't exist, causing timeouts that cascade into full IPC death.
//
const PER_PERSONA_COLLECTIONS = new Set<string>([
COLLECTIONS.MEMORIES,
COLLECTIONS.PERSONA_RAG_CONTEXTS,
COLLECTIONS.TIMELINE_EVENTS,
COLLECTIONS.COGNITION_STATE_SNAPSHOTS,
COLLECTIONS.COGNITION_PLAN_RECORDS,
COLLECTIONS.COGNITION_PLAN_STEP_EXECUTIONS,
COLLECTIONS.COGNITION_SELF_STATE_UPDATES,
COLLECTIONS.COGNITION_MEMORY_OPERATIONS,
COLLECTIONS.COGNITION_PLAN_REPLANS,
COLLECTIONS.TOOL_EXECUTION_LOGS,
COLLECTIONS.ADAPTER_DECISION_LOGS,
COLLECTIONS.ADAPTER_REASONING_LOGS,
COLLECTIONS.RESPONSE_GENERATION_LOGS,
]);

/**
* Check if a collection is per-persona only (never in main DB)
*/
export function isPerPersonaCollection(collection: string): boolean {
return PER_PERSONA_COLLECTIONS.has(collection);
}

/**
* Resolve dbHandle for a data command, FAILING for per-persona collections
* that don't provide a handle. Replaces the dangerous `params.dbHandle ?? 'default'`
* pattern that silently routes per-persona data to the main database.
*/
export function resolveDbHandle(collection: string, dbHandle?: string): string {
// If an explicit per-persona handle is provided, use it
if (dbHandle && dbHandle !== 'default') return dbHandle;

// Per-persona collections MUST have a real (non-default) handle.
// 'default' routes to the main shared DB where these tables don't exist.
if (PER_PERSONA_COLLECTIONS.has(collection)) {
throw new Error(
`Collection '${collection}' is per-persona only and requires a dbHandle. ` +
`Querying per-persona data on the main database is a bug. ` +
`Pass the persona's dbHandle from Hippocampus or data/open.`
);
}

return dbHandle || 'default';
}

// Re-export for convenience
export { COLLECTIONS, type CollectionName };
2 changes: 1 addition & 1 deletion src/generated-command-schemas.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"generated": "2026-03-01T00:44:05.568Z",
"generated": "2026-03-01T05:48:40.817Z",
"version": "1.0.0",
"commands": [
{
Expand Down
4 changes: 2 additions & 2 deletions src/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@continuum/jtag",
"version": "1.0.8430",
"version": "1.0.8444",
"description": "Global CLI debugging system for any Node.js project. Install once globally, use anywhere: npm install -g @continuum/jtag",
"config": {
"active_example": "widget-ui",
Expand Down
40 changes: 23 additions & 17 deletions src/scripts/parallel-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,29 @@ preflight_check_xcode
# - Build, start everything fresh, open new browser

HOT_RESTART=false
if ./jtag ping --timeout=3000 >/dev/null 2>&1; then
HOT_RESTART=true
echo -e "${GREEN}Phase 1: System already running — hot restart (browser preserved)${NC}"
# Kill ONLY the orchestrator process — leave browser, sockets, ports alone.
# The browser tab keeps its WebSocket open to the old server until it dies,
# then reconnects when the new server starts on the same port.
for pattern in "launch-active-example" "minimal-server" "launch-and-capture"; do
while IFS= read -r pid; do
if [ -n "$pid" ] && [ "$pid" != "$$" ] && [ "$pid" != "$PPID" ]; then
echo -e " Killing orchestrator ($pattern PID $pid)"
kill "$pid" 2>/dev/null || true
fi
done < <(pgrep -f "$pattern" 2>/dev/null || true)
done
# Brief pause for orchestrator to die — NOT the full nuclear 2s sleep
sleep 1
else
if [ -f .continuum/jtag/logs/system/npm-start.pid ]; then
OLD_PID=$(cat .continuum/jtag/logs/system/npm-start.pid)
if kill -0 "$OLD_PID" 2>/dev/null; then
HOT_RESTART=true
echo -e "${GREEN}Phase 1: System running (PID $OLD_PID) — hot restart (browser preserved)${NC}"
# Kill the known orchestrator PID — leave browser, sockets, ports alone.
# The browser tab keeps its WebSocket open to the old server until it dies,
# then reconnects when the new server starts on the same port.
kill "$OLD_PID" 2>/dev/null || true
# Also kill any other orchestrator patterns (safety — catches orphaned processes)
for pattern in "launch-active-example" "minimal-server" "launch-and-capture"; do
while IFS= read -r pid; do
if [ -n "$pid" ] && [ "$pid" != "$$" ] && [ "$pid" != "$PPID" ]; then
kill "$pid" 2>/dev/null || true
fi
done < <(pgrep -f "$pattern" 2>/dev/null || true)
done
# Brief pause for orchestrator to die — NOT the full nuclear 2s sleep
sleep 1
fi
fi

if [ "$HOT_RESTART" = false ]; then
echo -e "${YELLOW}Phase 1: No system running — cold start${NC}"
bash scripts/system-stop.sh
fi
Expand Down
4 changes: 2 additions & 2 deletions src/scripts/system-stop.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ for session in $(tmux list-sessions -F '#{session_name}' 2>/dev/null | grep '^jt
tmux kill-session -t "$session" 2>/dev/null || true
done

# 2. Kill LiveKit server
# 2. Kill LiveKit server (SIGKILL — holds UDP ports that linger on SIGTERM)
if pgrep -f "livekit-server" > /dev/null 2>&1; then
echo -e " Stopping LiveKit server..."
pkill -f "livekit-server" 2>/dev/null || true
pkill -9 -f "livekit-server" 2>/dev/null || true
fi

# 3. Kill Rust workers (from workers-config.json)
Expand Down
2 changes: 1 addition & 1 deletion src/shared/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
* DO NOT EDIT MANUALLY
*/

export const VERSION = '1.0.8430';
export const VERSION = '1.0.8444';
export const PACKAGE_NAME = '@continuum/jtag';
9 changes: 5 additions & 4 deletions src/system/rag/sources/ToolDefinitionsSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,11 @@ export class ToolDefinitionsSource implements RAGSource {
: [
'collaboration/chat/', // Communication is #1
'code/', // Code abilities are #2
'collaboration/decision/', // Decision making #3
'collaboration/wall/', // Shared documents #4
'data/', // Data access #5
'ai/', // AI meta-tools #6 (least important essential)
'sentinel/', // Autonomous coding (Claude Code) #3
'collaboration/decision/', // Decision making #4
'collaboration/wall/', // Shared documents #5
'data/', // Data access #6
'ai/', // AI meta-tools #7 (least important essential)
];

const recipe: ToolDefinition[] = [];
Expand Down
6 changes: 5 additions & 1 deletion src/system/user/server/modules/PersonaTaskExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,12 @@ export class PersonaTaskExecutor {
};

// 5. Store to persona's longterm.db (not shared database)
const memoryDbHandle = CognitionLogger.getDbHandle(this.personaId);
if (!memoryDbHandle) {
this.log(`⏳ ${this.displayName}: Skipping memory store — longterm.db not ready yet`);
continue;
}
try {
const memoryDbHandle = CognitionLogger.getDbHandle(this.personaId);
await Commands.execute('data/create', {
dbHandle: memoryDbHandle,
collection: COLLECTIONS.MEMORIES,
Expand Down
Loading
Loading