@tiflis-io/tiflis-code-workstation 0.3.9 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.js +204 -93
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -94,7 +94,7 @@ var EnvSchema = z.object({
94
94
  // ─────────────────────────────────────────────────────────────
95
95
  // Speech-to-Text (STT) Configuration
96
96
  // ─────────────────────────────────────────────────────────────
97
- STT_PROVIDER: z.enum(["openai", "elevenlabs", "deepgram"]).default("openai"),
97
+ STT_PROVIDER: z.enum(["openai", "elevenlabs", "deepgram", "local"]).default("openai"),
98
98
  STT_API_KEY: z.string().optional(),
99
99
  STT_MODEL: z.string().default("whisper-1"),
100
100
  STT_BASE_URL: z.string().url().optional(),
@@ -102,7 +102,7 @@ var EnvSchema = z.object({
102
102
  // ─────────────────────────────────────────────────────────────
103
103
  // Text-to-Speech (TTS) Configuration
104
104
  // ─────────────────────────────────────────────────────────────
105
- TTS_PROVIDER: z.enum(["openai", "elevenlabs"]).default("openai"),
105
+ TTS_PROVIDER: z.enum(["openai", "elevenlabs", "local"]).default("openai"),
106
106
  TTS_API_KEY: z.string().optional(),
107
107
  TTS_MODEL: z.string().default("tts-1"),
108
108
  TTS_VOICE: z.string().default("alloy"),
@@ -118,11 +118,11 @@ var EnvSchema = z.object({
118
118
  // Hide base agent options in mobile apps (only show aliases)
119
119
  // ─────────────────────────────────────────────────────────────
120
120
  /** Hide base Cursor agent option (only show aliases) */
121
- HIDE_BASE_CURSOR: z.string().transform((val) => val?.toLowerCase() === "true").default("false"),
121
+ HIDE_BASE_CURSOR: z.string().transform((val) => val.toLowerCase() === "true").default("false"),
122
122
  /** Hide base Claude agent option (only show aliases) */
123
- HIDE_BASE_CLAUDE: z.string().transform((val) => val?.toLowerCase() === "true").default("false"),
123
+ HIDE_BASE_CLAUDE: z.string().transform((val) => val.toLowerCase() === "true").default("false"),
124
124
  /** Hide base OpenCode agent option (only show aliases) */
125
- HIDE_BASE_OPENCODE: z.string().transform((val) => val?.toLowerCase() === "true").default("false"),
125
+ HIDE_BASE_OPENCODE: z.string().transform((val) => val.toLowerCase() === "true").default("false"),
126
126
  // ─────────────────────────────────────────────────────────────
127
127
  // Terminal Configuration
128
128
  // ─────────────────────────────────────────────────────────────
@@ -134,7 +134,7 @@ var EnvSchema = z.object({
134
134
  // Mock Mode Configuration (for screenshot automation)
135
135
  // ─────────────────────────────────────────────────────────────
136
136
  /** Enable mock mode for screenshot automation tests */
137
- MOCK_MODE: z.string().transform((val) => val?.toLowerCase() === "true").default("false"),
137
+ MOCK_MODE: z.string().transform((val) => val.toLowerCase() === "true").default("false"),
138
138
  /** Path to mock fixtures directory (defaults to built-in fixtures) */
139
139
  MOCK_FIXTURES_PATH: z.string().optional()
140
140
  });
@@ -155,6 +155,7 @@ function parseAgentAliases() {
155
155
  let commandStartIndex = 0;
156
156
  for (let i = 0; i < parts.length; i++) {
157
157
  const part = parts[i];
158
+ if (!part) break;
158
159
  const eqIndex = part.indexOf("=");
159
160
  if (eqIndex > 0 && !part.startsWith("-")) {
160
161
  const varName = part.slice(0, eqIndex);
@@ -173,6 +174,10 @@ function parseAgentAliases() {
173
174
  continue;
174
175
  }
175
176
  const baseCommand = commandParts[0];
177
+ if (!baseCommand) {
178
+ console.warn(`Invalid agent alias ${key}: empty base command`);
179
+ continue;
180
+ }
176
181
  const additionalArgs = commandParts.slice(1);
177
182
  aliases.set(aliasName, {
178
183
  name: aliasName,
@@ -2315,7 +2320,7 @@ var FileSystemWorkspaceDiscovery = class {
2315
2320
  }
2316
2321
  try {
2317
2322
  execSync(`git merge "${sourceBranch}"`, { cwd: projectPath });
2318
- } catch (error) {
2323
+ } catch {
2319
2324
  const conflicts = execSync("git diff --name-only --diff-filter=U", {
2320
2325
  cwd: projectPath,
2321
2326
  encoding: "utf-8"
@@ -3283,8 +3288,8 @@ function mergeToolBlocks(blocks) {
3283
3288
  metadata: {
3284
3289
  tool_name: block.metadata.tool_name || existing.metadata.tool_name,
3285
3290
  tool_use_id: toolUseId,
3286
- tool_input: block.metadata.tool_input || existing.metadata.tool_input,
3287
- tool_output: block.metadata.tool_output || existing.metadata.tool_output,
3291
+ tool_input: block.metadata.tool_input ?? existing.metadata.tool_input,
3292
+ tool_output: block.metadata.tool_output ?? existing.metadata.tool_output,
3288
3293
  tool_status: mergedStatus
3289
3294
  }
3290
3295
  };
@@ -3341,8 +3346,8 @@ function accumulateBlocks(existing, newBlocks) {
3341
3346
  metadata: {
3342
3347
  tool_name: block.metadata.tool_name || existingBlock.metadata.tool_name,
3343
3348
  tool_use_id: toolUseId,
3344
- tool_input: block.metadata.tool_input || existingBlock.metadata.tool_input,
3345
- tool_output: block.metadata.tool_output || existingBlock.metadata.tool_output,
3349
+ tool_input: block.metadata.tool_input ?? existingBlock.metadata.tool_input,
3350
+ tool_output: block.metadata.tool_output ?? existingBlock.metadata.tool_output,
3346
3351
  tool_status: mergedStatus
3347
3352
  }
3348
3353
  };
@@ -4022,7 +4027,7 @@ var AgentSessionManager = class extends EventEmitter2 {
4022
4027
  const worktreePath = `/${workspace}/${project}--${branch}`;
4023
4028
  const terminatedSessions = [];
4024
4029
  for (const [sessionId, state] of this.sessions) {
4025
- const isInWorktree = state.workingDir.includes(worktreePath) || state.cliSessionId?.includes(`${project}--${branch}`) || state.workingDir.endsWith(`${project}--${branch}`);
4030
+ const isInWorktree = state.workingDir.includes(worktreePath) || (state.cliSessionId?.includes(`${project}--${branch}`) ?? false) || state.workingDir.endsWith(`${project}--${branch}`);
4026
4031
  if (isInWorktree) {
4027
4032
  try {
4028
4033
  if (state.isExecuting) {
@@ -4044,7 +4049,7 @@ var AgentSessionManager = class extends EventEmitter2 {
4044
4049
  getWorktreeSessionSummary(workspace, project, branch) {
4045
4050
  const worktreePath = `/${workspace}/${project}--${branch}`;
4046
4051
  const activeSessions = Array.from(this.sessions.values()).filter(
4047
- (session) => session.workingDir.includes(worktreePath) || session.cliSessionId?.includes(`${project}--${branch}`) || session.workingDir.endsWith(`${project}--${branch}`)
4052
+ (session) => session.workingDir.includes(worktreePath) || (session.cliSessionId?.includes(`${project}--${branch}`) ?? false) || session.workingDir.endsWith(`${project}--${branch}`)
4048
4053
  );
4049
4054
  const sessionTypes = [...new Set(activeSessions.map((s) => s.agentType))];
4050
4055
  const executingCount = activeSessions.filter((s) => s.isExecuting).length;
@@ -4691,6 +4696,9 @@ var TerminateSessionUseCase = class {
4691
4696
  let terminatedInMemory = false;
4692
4697
  let terminatedInDb = false;
4693
4698
  if (session) {
4699
+ if (isAgentType(session.type)) {
4700
+ this.deps.agentSessionManager.terminateSession(sessionId);
4701
+ }
4694
4702
  await this.deps.sessionManager.terminateSession(id);
4695
4703
  terminatedInMemory = true;
4696
4704
  this.logger.info(
@@ -4729,6 +4737,42 @@ var TerminateSessionUseCase = class {
4729
4737
  };
4730
4738
  return { response, broadcast };
4731
4739
  }
4740
+ /**
4741
+ * Terminates a session and broadcasts the termination to all clients.
4742
+ * Use this for internal calls (e.g., from supervisor tools) where no request/response is needed.
4743
+ * Returns true if session was found and terminated, false otherwise.
4744
+ */
4745
+ async terminateAndBroadcast(sessionId) {
4746
+ const id = new SessionId(sessionId);
4747
+ this.logger.info({ sessionId }, "Attempting to terminate session (internal)");
4748
+ const session = this.deps.sessionManager.getSession(id);
4749
+ if (session?.type === "supervisor") {
4750
+ throw new Error("Cannot terminate supervisor session");
4751
+ }
4752
+ let terminatedInMemory = false;
4753
+ let terminatedInDb = false;
4754
+ if (session) {
4755
+ if (isAgentType(session.type)) {
4756
+ this.deps.agentSessionManager.terminateSession(sessionId);
4757
+ }
4758
+ await this.deps.sessionManager.terminateSession(id);
4759
+ terminatedInMemory = true;
4760
+ }
4761
+ terminatedInDb = this.deps.chatHistoryService.terminateSession(sessionId);
4762
+ if (!terminatedInMemory && !terminatedInDb) {
4763
+ return false;
4764
+ }
4765
+ const broadcast = {
4766
+ type: "session.terminated",
4767
+ session_id: sessionId
4768
+ };
4769
+ this.deps.messageBroadcaster.broadcastToAll(JSON.stringify(broadcast));
4770
+ this.logger.info(
4771
+ { sessionId, terminatedInMemory, terminatedInDb },
4772
+ "Session terminated and broadcast sent"
4773
+ );
4774
+ return true;
4775
+ }
4732
4776
  };
4733
4777
 
4734
4778
  // src/application/queries/list-sessions.ts
@@ -6857,16 +6901,7 @@ Steps executed:
6857
6901
  // src/infrastructure/agents/supervisor/tools/session-tools.ts
6858
6902
  import { tool as tool3 } from "@langchain/core/tools";
6859
6903
  import { z as z5 } from "zod";
6860
- function createSessionTools(sessionManager, agentSessionManager, workspaceDiscovery, workspacesRoot, getMessageBroadcaster, getChatHistoryService) {
6861
- const broadcastTermination = (sessionId) => {
6862
- const broadcaster = getMessageBroadcaster?.();
6863
- if (!broadcaster) return;
6864
- const message = {
6865
- type: "session.terminated",
6866
- session_id: sessionId
6867
- };
6868
- broadcaster.broadcastToAll(JSON.stringify(message));
6869
- };
6904
+ function createSessionTools(sessionManager, agentSessionManager, workspaceDiscovery, workspacesRoot, _getMessageBroadcaster, getChatHistoryService, clearSupervisorContext, terminateSessionCallback) {
6870
6905
  const listSessions = tool3(
6871
6906
  () => {
6872
6907
  const chatHistoryService = getChatHistoryService?.();
@@ -7008,26 +7043,13 @@ Use this tool when user asks to "open terminal", "create terminal", or similar r
7008
7043
  const terminateSession = tool3(
7009
7044
  async ({ sessionId }) => {
7010
7045
  try {
7011
- const { SessionId: SessionId2 } = await import("./session-id-VKOYWZAK.js");
7012
- const id = new SessionId2(sessionId);
7013
- const session = sessionManager.getSession(id);
7014
- let terminatedInMemory = false;
7015
- let terminatedInDb = false;
7016
- if (session) {
7017
- if (isAgentType(session.type)) {
7018
- agentSessionManager.terminateSession(sessionId);
7019
- }
7020
- await sessionManager.terminateSession(id);
7021
- terminatedInMemory = true;
7022
- }
7023
- const chatHistoryService = getChatHistoryService?.();
7024
- if (chatHistoryService) {
7025
- terminatedInDb = chatHistoryService.terminateSession(sessionId);
7046
+ if (!terminateSessionCallback) {
7047
+ return "Error: Terminate session callback not configured.";
7026
7048
  }
7027
- if (!terminatedInMemory && !terminatedInDb) {
7049
+ const terminated = await terminateSessionCallback(sessionId);
7050
+ if (!terminated) {
7028
7051
  return `Session "${sessionId}" not found.`;
7029
7052
  }
7030
- broadcastTermination(sessionId);
7031
7053
  return `Session "${sessionId}" terminated.`;
7032
7054
  } catch (error) {
7033
7055
  return `Error terminating session: ${error instanceof Error ? error.message : String(error)}`;
@@ -7044,6 +7066,9 @@ Use this tool when user asks to "open terminal", "create terminal", or similar r
7044
7066
  const terminateAllSessions = tool3(
7045
7067
  async ({ sessionType }) => {
7046
7068
  try {
7069
+ if (!terminateSessionCallback) {
7070
+ return "Error: Terminate session callback not configured.";
7071
+ }
7047
7072
  const typeFilter = sessionType === "all" || !sessionType ? null : sessionType;
7048
7073
  const chatHistoryService = getChatHistoryService?.();
7049
7074
  const inMemorySessions = sessionManager.getAllSessions();
@@ -7051,31 +7076,25 @@ Use this tool when user asks to "open terminal", "create terminal", or similar r
7051
7076
  const persistedSessions = chatHistoryService?.getActiveAgentSessions() ?? [];
7052
7077
  const inMemoryIds = new Set(inMemorySessions.map((s) => s.id.value));
7053
7078
  const persistedToTerminate = persistedSessions.filter((s) => !inMemoryIds.has(s.sessionId)).filter((s) => !typeFilter || s.sessionType === typeFilter);
7054
- if (inMemoryToTerminate.length === 0 && persistedToTerminate.length === 0) {
7079
+ const sessionsToTerminate = [
7080
+ ...inMemoryToTerminate.map((s) => ({ id: s.id.value, type: s.type })),
7081
+ ...persistedToTerminate.map((s) => ({ id: s.sessionId, type: s.sessionType }))
7082
+ ];
7083
+ if (sessionsToTerminate.length === 0) {
7055
7084
  return typeFilter ? `No active ${typeFilter} sessions to terminate.` : "No active sessions to terminate.";
7056
7085
  }
7057
7086
  const terminated = [];
7058
7087
  const errors = [];
7059
- for (const session of inMemoryToTerminate) {
7088
+ for (const session of sessionsToTerminate) {
7060
7089
  try {
7061
- if (isAgentType(session.type)) {
7062
- agentSessionManager.terminateSession(session.id.value);
7090
+ const success = await terminateSessionCallback(session.id);
7091
+ if (success) {
7092
+ terminated.push(`${session.type}:${session.id}`);
7093
+ } else {
7094
+ errors.push(`${session.id}: Session not found`);
7063
7095
  }
7064
- await sessionManager.terminateSession(session.id);
7065
- chatHistoryService?.terminateSession(session.id.value);
7066
- broadcastTermination(session.id.value);
7067
- terminated.push(`${session.type}:${session.id.value}`);
7068
7096
  } catch (error) {
7069
- errors.push(`${session.id.value}: ${error instanceof Error ? error.message : String(error)}`);
7070
- }
7071
- }
7072
- for (const session of persistedToTerminate) {
7073
- try {
7074
- chatHistoryService?.terminateSession(session.sessionId);
7075
- broadcastTermination(session.sessionId);
7076
- terminated.push(`${session.sessionType}:${session.sessionId}`);
7077
- } catch (error) {
7078
- errors.push(`${session.sessionId}: ${error instanceof Error ? error.message : String(error)}`);
7097
+ errors.push(`${session.id}: ${error instanceof Error ? error.message : String(error)}`);
7079
7098
  }
7080
7099
  }
7081
7100
  let result = `Terminated ${terminated.length} session(s)`;
@@ -7220,6 +7239,23 @@ ${terminatedSessions.map((id) => ` - ${id}`).join("\n")}`;
7220
7239
  })
7221
7240
  }
7222
7241
  );
7242
+ const clearContext = tool3(
7243
+ () => {
7244
+ try {
7245
+ if (clearSupervisorContext) {
7246
+ clearSupervisorContext();
7247
+ }
7248
+ return "Supervisor context cleared successfully. Conversation history has been reset.";
7249
+ } catch (error) {
7250
+ return `Error clearing context: ${error instanceof Error ? error.message : String(error)}`;
7251
+ }
7252
+ },
7253
+ {
7254
+ name: "clear_supervisor_context",
7255
+ description: 'Clears the supervisor agent conversation context and history. Use when user asks to "clear context", "reset conversation", "start fresh", or similar requests.',
7256
+ schema: z5.object({})
7257
+ }
7258
+ );
7223
7259
  return [
7224
7260
  listSessions,
7225
7261
  listAvailableAgents,
@@ -7230,7 +7266,8 @@ ${terminatedSessions.map((id) => ` - ${id}`).join("\n")}`;
7230
7266
  getSessionInfo,
7231
7267
  listSessionsWithWorktrees,
7232
7268
  getWorktreeSessionSummary,
7233
- terminateWorktreeSessions
7269
+ terminateWorktreeSessions,
7270
+ clearContext
7234
7271
  ];
7235
7272
  }
7236
7273
 
@@ -7348,6 +7385,8 @@ function formatSize(bytes) {
7348
7385
  var SupervisorAgent = class extends EventEmitter4 {
7349
7386
  logger;
7350
7387
  agent;
7388
+ getMessageBroadcaster;
7389
+ getChatHistoryService;
7351
7390
  conversationHistory = [];
7352
7391
  abortController = null;
7353
7392
  isExecuting = false;
@@ -7359,8 +7398,18 @@ var SupervisorAgent = class extends EventEmitter4 {
7359
7398
  constructor(config2) {
7360
7399
  super();
7361
7400
  this.logger = config2.logger.child({ component: "SupervisorAgent" });
7401
+ this.getMessageBroadcaster = config2.getMessageBroadcaster;
7402
+ this.getChatHistoryService = config2.getChatHistoryService;
7362
7403
  const env = getEnv();
7363
7404
  const llm = this.createLLM(env);
7405
+ const terminateSessionCallback = async (sessionId) => {
7406
+ const terminate = config2.getTerminateSession?.();
7407
+ if (!terminate) {
7408
+ this.logger.warn("Terminate session callback not available");
7409
+ return false;
7410
+ }
7411
+ return terminate(sessionId);
7412
+ };
7364
7413
  const tools = [
7365
7414
  ...createWorkspaceTools(config2.workspaceDiscovery),
7366
7415
  ...createWorktreeTools(config2.workspaceDiscovery, config2.agentSessionManager),
@@ -7370,7 +7419,9 @@ var SupervisorAgent = class extends EventEmitter4 {
7370
7419
  config2.workspaceDiscovery,
7371
7420
  config2.workspacesRoot,
7372
7421
  config2.getMessageBroadcaster,
7373
- config2.getChatHistoryService
7422
+ config2.getChatHistoryService,
7423
+ () => this.clearContext(),
7424
+ terminateSessionCallback
7374
7425
  ),
7375
7426
  ...createFilesystemTools(config2.workspacesRoot)
7376
7427
  ];
@@ -7621,14 +7672,38 @@ var SupervisorAgent = class extends EventEmitter4 {
7621
7672
  this.logger.debug("Ended command processing");
7622
7673
  }
7623
7674
  /**
7624
- * Clears global conversation history.
7675
+ * Clears global conversation history (in-memory only).
7625
7676
  * Also resets cancellation state to allow new commands.
7677
+ * @deprecated Use clearContext() for full context clearing with persistence and broadcast.
7626
7678
  */
7627
7679
  clearHistory() {
7628
7680
  this.conversationHistory = [];
7629
7681
  this.isCancelled = false;
7630
7682
  this.logger.info("Global conversation history cleared");
7631
7683
  }
7684
+ /**
7685
+ * Clears supervisor context completely:
7686
+ * - In-memory conversation history
7687
+ * - Persistent history in database
7688
+ * - Notifies all connected clients
7689
+ */
7690
+ clearContext() {
7691
+ this.conversationHistory = [];
7692
+ this.isCancelled = false;
7693
+ const chatHistoryService = this.getChatHistoryService?.();
7694
+ if (chatHistoryService) {
7695
+ chatHistoryService.clearSupervisorHistory();
7696
+ }
7697
+ const broadcaster = this.getMessageBroadcaster?.();
7698
+ if (broadcaster) {
7699
+ const clearNotification = JSON.stringify({
7700
+ type: "supervisor.context_cleared",
7701
+ payload: { timestamp: Date.now() }
7702
+ });
7703
+ broadcaster.broadcastToAll(clearNotification);
7704
+ }
7705
+ this.logger.info("Supervisor context cleared (in-memory, persistent, and clients notified)");
7706
+ }
7632
7707
  /**
7633
7708
  * Resets the cancellation state.
7634
7709
  * Call this before starting a new command to ensure previous cancellation doesn't affect it.
@@ -7864,8 +7939,9 @@ var DEFAULT_FIXTURES_PATH = join7(__dirname, "fixtures");
7864
7939
  var fixtureCache = /* @__PURE__ */ new Map();
7865
7940
  function loadFixture(name, customPath) {
7866
7941
  const cacheKey = `${customPath ?? "default"}:${name}`;
7867
- if (fixtureCache.has(cacheKey)) {
7868
- return fixtureCache.get(cacheKey);
7942
+ const cached = fixtureCache.get(cacheKey);
7943
+ if (cached) {
7944
+ return cached;
7869
7945
  }
7870
7946
  const fixturesDir = customPath ?? DEFAULT_FIXTURES_PATH;
7871
7947
  const filePath = join7(fixturesDir, `${name}.json`);
@@ -7901,20 +7977,14 @@ async function simulateStreaming(text2, delayMs = DEFAULT_TOKEN_DELAY_MS, onBloc
7901
7977
  const tokens = tokenize(text2);
7902
7978
  let accumulated = "";
7903
7979
  for (let i = 0; i < tokens.length; i++) {
7904
- accumulated += tokens[i];
7905
- const block = {
7906
- type: "text",
7907
- text: accumulated
7908
- };
7980
+ accumulated += tokens[i] ?? "";
7981
+ const block = createTextBlock(accumulated);
7909
7982
  onBlock([block], false);
7910
7983
  if (i < tokens.length - 1) {
7911
7984
  await sleep(delayMs);
7912
7985
  }
7913
7986
  }
7914
- const finalBlock = {
7915
- type: "text",
7916
- text: accumulated
7917
- };
7987
+ const finalBlock = createTextBlock(accumulated);
7918
7988
  onBlock([finalBlock], true);
7919
7989
  onComplete();
7920
7990
  }
@@ -7976,7 +8046,7 @@ var MockSupervisorAgent = class extends EventEmitter5 {
7976
8046
  /**
7977
8047
  * Executes a command (non-streaming).
7978
8048
  */
7979
- async execute(command, deviceId, _currentSessionId) {
8049
+ execute(command, deviceId, _currentSessionId) {
7980
8050
  this.logger.info({ command, deviceId }, "Mock supervisor execute");
7981
8051
  const response = this.getResponse(command);
7982
8052
  this.conversationHistory.push({ role: "user", content: command });
@@ -8379,7 +8449,18 @@ var STTService = class {
8379
8449
  this.logger.debug({ audioSize: audioBuffer.length, format }, "Transcribing audio");
8380
8450
  const startTime = Date.now();
8381
8451
  try {
8382
- const result = this.config.provider === "openai" ? await this.transcribeOpenAI(audioBuffer, format, signal) : await this.transcribeElevenLabs(audioBuffer, format, signal);
8452
+ let result;
8453
+ switch (this.config.provider) {
8454
+ case "openai":
8455
+ case "local":
8456
+ result = await this.transcribeOpenAI(audioBuffer, format, signal);
8457
+ break;
8458
+ case "elevenlabs":
8459
+ result = await this.transcribeElevenLabs(audioBuffer, format, signal);
8460
+ break;
8461
+ default:
8462
+ throw new Error(`Unsupported STT provider: ${this.config.provider}`);
8463
+ }
8383
8464
  const elapsed = Date.now() - startTime;
8384
8465
  this.logger.info(
8385
8466
  {
@@ -8491,6 +8572,9 @@ var STTService = class {
8491
8572
  * Check if the service is configured and ready.
8492
8573
  */
8493
8574
  isConfigured() {
8575
+ if (this.config.provider === "local") {
8576
+ return Boolean(this.config.baseUrl && this.config.model);
8577
+ }
8494
8578
  return Boolean(this.config.apiKey && this.config.model);
8495
8579
  }
8496
8580
  /**
@@ -8507,20 +8591,28 @@ var STTService = class {
8507
8591
  function createSTTService(env, logger) {
8508
8592
  const provider = (env.STT_PROVIDER ?? "openai").toLowerCase();
8509
8593
  const apiKey = env.STT_API_KEY;
8510
- if (!apiKey) {
8594
+ const baseUrl = env.STT_BASE_URL;
8595
+ if (provider === "local") {
8596
+ if (!baseUrl) {
8597
+ logger.warn("STT_BASE_URL not configured for local provider, STT service disabled");
8598
+ return null;
8599
+ }
8600
+ } else if (!apiKey) {
8511
8601
  logger.warn("STT_API_KEY not configured, STT service disabled");
8512
8602
  return null;
8513
8603
  }
8514
8604
  const defaults = {
8515
8605
  openai: { model: "whisper-1" },
8516
- elevenlabs: { model: "scribe_v1" }
8606
+ elevenlabs: { model: "scribe_v1" },
8607
+ deepgram: { model: "nova-2" },
8608
+ local: { model: "large-v3" }
8517
8609
  };
8518
8610
  const config2 = {
8519
8611
  provider,
8520
- apiKey,
8612
+ apiKey: apiKey ?? "",
8521
8613
  model: env.STT_MODEL ?? defaults[provider].model,
8522
8614
  language: env.STT_LANGUAGE,
8523
- baseUrl: env.STT_BASE_URL
8615
+ baseUrl
8524
8616
  };
8525
8617
  return new STTService(config2, logger);
8526
8618
  }
@@ -8540,7 +8632,20 @@ var TTSService = class {
8540
8632
  this.logger.debug({ textLength: text2.length }, "Synthesizing speech");
8541
8633
  const startTime = Date.now();
8542
8634
  try {
8543
- const result = this.config.provider === "openai" ? await this.synthesizeOpenAI(text2) : await this.synthesizeElevenLabs(text2);
8635
+ let result;
8636
+ switch (this.config.provider) {
8637
+ case "openai":
8638
+ case "local":
8639
+ result = await this.synthesizeOpenAI(text2);
8640
+ break;
8641
+ case "elevenlabs":
8642
+ result = await this.synthesizeElevenLabs(text2);
8643
+ break;
8644
+ default: {
8645
+ const exhaustiveCheck = this.config.provider;
8646
+ throw new Error(`Unsupported TTS provider: ${exhaustiveCheck}`);
8647
+ }
8648
+ }
8544
8649
  const elapsed = Date.now() - startTime;
8545
8650
  this.logger.info(
8546
8651
  { textLength: text2.length, audioSize: result.audio.length, elapsedMs: elapsed },
@@ -8617,6 +8722,9 @@ var TTSService = class {
8617
8722
  * Check if the service is configured and ready.
8618
8723
  */
8619
8724
  isConfigured() {
8725
+ if (this.config.provider === "local") {
8726
+ return Boolean(this.config.baseUrl && this.config.voice);
8727
+ }
8620
8728
  return Boolean(this.config.apiKey && this.config.model && this.config.voice);
8621
8729
  }
8622
8730
  /**
@@ -8633,20 +8741,27 @@ var TTSService = class {
8633
8741
  function createTTSService(env, logger) {
8634
8742
  const provider = (env.TTS_PROVIDER ?? "openai").toLowerCase();
8635
8743
  const apiKey = env.TTS_API_KEY;
8636
- if (!apiKey) {
8744
+ const baseUrl = env.TTS_BASE_URL;
8745
+ if (provider === "local") {
8746
+ if (!baseUrl) {
8747
+ logger.warn("TTS_BASE_URL not configured for local provider, TTS service disabled");
8748
+ return null;
8749
+ }
8750
+ } else if (!apiKey) {
8637
8751
  logger.warn("TTS_API_KEY not configured, TTS service disabled");
8638
8752
  return null;
8639
8753
  }
8640
8754
  const defaults = {
8641
8755
  openai: { model: "tts-1", voice: "alloy" },
8642
- elevenlabs: { model: "eleven_flash_v2_5", voice: "21m00Tcm4TlvDq8ikWAM" }
8756
+ elevenlabs: { model: "eleven_flash_v2_5", voice: "21m00Tcm4TlvDq8ikWAM" },
8757
+ local: { model: "kokoro", voice: "af_heart" }
8643
8758
  };
8644
8759
  const config2 = {
8645
8760
  provider,
8646
- apiKey,
8761
+ apiKey: apiKey ?? "",
8647
8762
  model: env.TTS_MODEL ?? defaults[provider].model,
8648
8763
  voice: env.TTS_VOICE ?? defaults[provider].voice,
8649
- baseUrl: env.TTS_BASE_URL
8764
+ baseUrl
8650
8765
  };
8651
8766
  return new TTSService(config2, logger);
8652
8767
  }
@@ -9066,7 +9181,8 @@ async function bootstrap() {
9066
9181
  workspacesRoot: env.WORKSPACES_ROOT,
9067
9182
  logger,
9068
9183
  getMessageBroadcaster: () => messageBroadcaster,
9069
- getChatHistoryService: () => chatHistoryService
9184
+ getChatHistoryService: () => chatHistoryService,
9185
+ getTerminateSession: () => terminateSession?.terminateAndBroadcast.bind(terminateSession) ?? null
9070
9186
  });
9071
9187
  if (env.MOCK_MODE) {
9072
9188
  logger.info("Mock Supervisor Agent initialized for screenshot automation");
@@ -9420,7 +9536,7 @@ async function bootstrap() {
9420
9536
  let commandText;
9421
9537
  const messageId = commandMessage.payload.message_id;
9422
9538
  if (messageBroadcaster) {
9423
- const ackMessageId = messageId || commandMessage.id;
9539
+ const ackMessageId = messageId ?? commandMessage.id;
9424
9540
  const ackMessage = {
9425
9541
  type: "message.ack",
9426
9542
  payload: {
@@ -9678,13 +9794,7 @@ async function bootstrap() {
9678
9794
  const tunnelClient2 = clearMessage.device_id ? clientRegistry.getByDeviceId(new DeviceId(clearMessage.device_id)) : void 0;
9679
9795
  const isAuthenticated = directClient?.isAuthenticated ?? tunnelClient2?.isAuthenticated;
9680
9796
  if (isAuthenticated) {
9681
- supervisorAgent.clearHistory();
9682
- chatHistoryService.clearSupervisorHistory();
9683
- const clearNotification = JSON.stringify({
9684
- type: "supervisor.context_cleared",
9685
- payload: { timestamp: Date.now() }
9686
- });
9687
- broadcaster.broadcastToAll(clearNotification);
9797
+ supervisorAgent.clearContext();
9688
9798
  sendToDevice(
9689
9799
  socket,
9690
9800
  clearMessage.device_id,
@@ -9907,7 +10017,7 @@ async function bootstrap() {
9907
10017
  const sessionId = execMessage.session_id;
9908
10018
  const messageId = execMessage.payload.message_id;
9909
10019
  if (deviceId && messageBroadcaster) {
9910
- const ackMessageId = messageId || execMessage.id;
10020
+ const ackMessageId = messageId ?? execMessage.id;
9911
10021
  const ackMessage = {
9912
10022
  type: "message.ack",
9913
10023
  payload: {
@@ -10657,6 +10767,7 @@ async function bootstrap() {
10657
10767
  });
10658
10768
  terminateSession = new TerminateSessionUseCase({
10659
10769
  sessionManager,
10770
+ agentSessionManager,
10660
10771
  messageBroadcaster,
10661
10772
  chatHistoryService,
10662
10773
  logger
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiflis-io/tiflis-code-workstation",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "description": "Workstation server for tiflis-code - manages agent sessions and terminal access",
5
5
  "author": "Roman Barinov <rbarinov@gmail.com>",
6
6
  "license": "FSL-1.1-NC",