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

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 +252 -53
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -247,18 +247,18 @@ function getProtocolVersion() {
247
247
  return `${PROTOCOL_VERSION.major}.${PROTOCOL_VERSION.minor}.${PROTOCOL_VERSION.patch}`;
248
248
  }
249
249
  var CONNECTION_TIMING = {
250
- /** How often to send ping to tunnel (15 seconds - keeps connection alive through proxies) */
251
- PING_INTERVAL_MS: 15e3,
252
- /** Max time to wait for pong before considering connection stale (30 seconds) */
253
- PONG_TIMEOUT_MS: 3e4,
254
- /** Max time to wait for registration response (15 seconds) */
255
- REGISTRATION_TIMEOUT_MS: 15e3,
256
- /** Minimum reconnect delay (1 second) */
257
- RECONNECT_DELAY_MIN_MS: 1e3,
258
- /** Maximum reconnect delay (30 seconds) */
259
- RECONNECT_DELAY_MAX_MS: 3e4,
260
- /** Interval for checking timed-out client connections (10 seconds) */
261
- CLIENT_TIMEOUT_CHECK_INTERVAL_MS: 1e4
250
+ /** How often to send ping to tunnel (5 seconds - fast liveness detection) */
251
+ PING_INTERVAL_MS: 5e3,
252
+ /** Max time to wait for pong before considering connection stale (10 seconds) */
253
+ PONG_TIMEOUT_MS: 1e4,
254
+ /** Max time to wait for registration response (10 seconds) */
255
+ REGISTRATION_TIMEOUT_MS: 1e4,
256
+ /** Minimum reconnect delay (500ms - fast first retry) */
257
+ RECONNECT_DELAY_MIN_MS: 500,
258
+ /** Maximum reconnect delay (5 seconds - don't wait too long) */
259
+ RECONNECT_DELAY_MAX_MS: 5e3,
260
+ /** Interval for checking timed-out client connections (5 seconds - faster cleanup) */
261
+ CLIENT_TIMEOUT_CHECK_INTERVAL_MS: 5e3
262
262
  };
263
263
  var SESSION_CONFIG = {
264
264
  /** Maximum number of concurrent agent sessions */
@@ -1895,13 +1895,37 @@ import { join as join4 } from "path";
1895
1895
  import { execSync } from "child_process";
1896
1896
  var FileSystemWorkspaceDiscovery = class {
1897
1897
  workspacesRoot;
1898
+ cacheTtlMs;
1899
+ /** Cache for workspace list */
1900
+ workspacesCache = null;
1901
+ /** Cache for projects by workspace name */
1902
+ projectsCache = /* @__PURE__ */ new Map();
1898
1903
  constructor(config2) {
1899
1904
  this.workspacesRoot = config2.workspacesRoot;
1905
+ this.cacheTtlMs = config2.cacheTtlMs ?? 3e4;
1906
+ }
1907
+ /**
1908
+ * Checks if a cache entry is still valid.
1909
+ */
1910
+ isCacheValid(entry) {
1911
+ if (!entry) return false;
1912
+ return Date.now() - entry.timestamp < this.cacheTtlMs;
1913
+ }
1914
+ /**
1915
+ * Invalidates all caches. Call when workspace structure changes.
1916
+ */
1917
+ invalidateCache() {
1918
+ this.workspacesCache = null;
1919
+ this.projectsCache.clear();
1900
1920
  }
1901
1921
  /**
1902
1922
  * Lists all workspaces in the workspaces root.
1923
+ * Results are cached for faster subsequent calls.
1903
1924
  */
1904
1925
  async listWorkspaces() {
1926
+ if (this.isCacheValid(this.workspacesCache)) {
1927
+ return this.workspacesCache.data;
1928
+ }
1905
1929
  const entries = await readdir(this.workspacesRoot, { withFileTypes: true });
1906
1930
  const workspaces = [];
1907
1931
  for (const entry of entries) {
@@ -1915,12 +1939,22 @@ var FileSystemWorkspaceDiscovery = class {
1915
1939
  });
1916
1940
  }
1917
1941
  }
1918
- return workspaces.sort((a, b) => a.name.localeCompare(b.name));
1942
+ const result = workspaces.sort((a, b) => a.name.localeCompare(b.name));
1943
+ this.workspacesCache = {
1944
+ data: result,
1945
+ timestamp: Date.now()
1946
+ };
1947
+ return result;
1919
1948
  }
1920
1949
  /**
1921
1950
  * Lists all projects in a workspace.
1951
+ * Results are cached for faster subsequent calls.
1922
1952
  */
1923
1953
  async listProjects(workspace) {
1954
+ const cached = this.projectsCache.get(workspace);
1955
+ if (this.isCacheValid(cached)) {
1956
+ return cached.data;
1957
+ }
1924
1958
  const workspacePath = join4(this.workspacesRoot, workspace);
1925
1959
  if (!await this.pathExists(workspacePath)) {
1926
1960
  return [];
@@ -1949,7 +1983,12 @@ var FileSystemWorkspaceDiscovery = class {
1949
1983
  });
1950
1984
  }
1951
1985
  }
1952
- return projects.sort((a, b) => a.name.localeCompare(b.name));
1986
+ const result = projects.sort((a, b) => a.name.localeCompare(b.name));
1987
+ this.projectsCache.set(workspace, {
1988
+ data: result,
1989
+ timestamp: Date.now()
1990
+ });
1991
+ return result;
1953
1992
  }
1954
1993
  /**
1955
1994
  * Gets information about a specific project.
@@ -3141,6 +3180,12 @@ var HeadlessAgentExecutor = class extends EventEmitter {
3141
3180
 
3142
3181
  // src/domain/value-objects/content-block.ts
3143
3182
  import { randomUUID } from "crypto";
3183
+ function isTextBlock(block) {
3184
+ return block.block_type === "text";
3185
+ }
3186
+ function isToolBlock(block) {
3187
+ return block.block_type === "tool";
3188
+ }
3144
3189
  function createTextBlock(content) {
3145
3190
  return {
3146
3191
  id: randomUUID(),
@@ -3219,6 +3264,103 @@ function createVoiceOutputBlock(text2, options) {
3219
3264
  }
3220
3265
  };
3221
3266
  }
3267
+ function mergeToolBlocks(blocks) {
3268
+ const toolBlocksByUseId = /* @__PURE__ */ new Map();
3269
+ for (const block of blocks) {
3270
+ if (isToolBlock(block) && block.metadata.tool_use_id) {
3271
+ const toolUseId = block.metadata.tool_use_id;
3272
+ const existing = toolBlocksByUseId.get(toolUseId);
3273
+ if (existing) {
3274
+ const mergedStatus = getMergedToolStatus(
3275
+ existing.metadata.tool_status,
3276
+ block.metadata.tool_status
3277
+ );
3278
+ const mergedBlock = {
3279
+ id: existing.id,
3280
+ // Keep original ID
3281
+ block_type: "tool",
3282
+ content: block.metadata.tool_name || existing.content,
3283
+ metadata: {
3284
+ tool_name: block.metadata.tool_name || existing.metadata.tool_name,
3285
+ 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,
3288
+ tool_status: mergedStatus
3289
+ }
3290
+ };
3291
+ toolBlocksByUseId.set(toolUseId, mergedBlock);
3292
+ } else {
3293
+ toolBlocksByUseId.set(toolUseId, block);
3294
+ }
3295
+ }
3296
+ }
3297
+ const seenToolUseIds = /* @__PURE__ */ new Set();
3298
+ const result = [];
3299
+ for (const block of blocks) {
3300
+ if (isToolBlock(block) && block.metadata.tool_use_id) {
3301
+ const toolUseId = block.metadata.tool_use_id;
3302
+ if (!seenToolUseIds.has(toolUseId)) {
3303
+ seenToolUseIds.add(toolUseId);
3304
+ const mergedBlock = toolBlocksByUseId.get(toolUseId);
3305
+ if (mergedBlock) {
3306
+ result.push(mergedBlock);
3307
+ }
3308
+ }
3309
+ } else {
3310
+ result.push(block);
3311
+ }
3312
+ }
3313
+ return result;
3314
+ }
3315
+ function getMergedToolStatus(status1, status2) {
3316
+ if (status1 === "completed" || status2 === "completed") {
3317
+ return "completed";
3318
+ }
3319
+ if (status1 === "failed" || status2 === "failed") {
3320
+ return "failed";
3321
+ }
3322
+ return "running";
3323
+ }
3324
+ function accumulateBlocks(existing, newBlocks) {
3325
+ for (const block of newBlocks) {
3326
+ if (isToolBlock(block) && block.metadata.tool_use_id) {
3327
+ const toolUseId = block.metadata.tool_use_id;
3328
+ const existingIndex = existing.findIndex(
3329
+ (b) => isToolBlock(b) && b.metadata.tool_use_id === toolUseId
3330
+ );
3331
+ if (existingIndex >= 0) {
3332
+ const existingBlock = existing[existingIndex];
3333
+ const mergedStatus = getMergedToolStatus(
3334
+ existingBlock.metadata.tool_status,
3335
+ block.metadata.tool_status
3336
+ );
3337
+ existing[existingIndex] = {
3338
+ id: existingBlock.id,
3339
+ block_type: "tool",
3340
+ content: block.metadata.tool_name || existingBlock.content,
3341
+ metadata: {
3342
+ tool_name: block.metadata.tool_name || existingBlock.metadata.tool_name,
3343
+ 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,
3346
+ tool_status: mergedStatus
3347
+ }
3348
+ };
3349
+ } else {
3350
+ existing.push(block);
3351
+ }
3352
+ } else if (isTextBlock(block)) {
3353
+ const lastBlock = existing[existing.length - 1];
3354
+ if (lastBlock && isTextBlock(lastBlock)) {
3355
+ existing[existing.length - 1] = block;
3356
+ } else {
3357
+ existing.push(block);
3358
+ }
3359
+ } else {
3360
+ existing.push(block);
3361
+ }
3362
+ }
3363
+ }
3222
3364
 
3223
3365
  // src/infrastructure/agents/agent-output-parser.ts
3224
3366
  var AgentOutputParser = class _AgentOutputParser {
@@ -5598,7 +5740,7 @@ var ChatHistoryService = class _ChatHistoryService {
5598
5740
  * Gets supervisor chat history (global, shared across all devices).
5599
5741
  * Returns messages sorted by sequence (oldest first) for chronological display.
5600
5742
  */
5601
- getSupervisorHistory(limit = 50) {
5743
+ getSupervisorHistory(limit = 20) {
5602
5744
  const sessionId = _ChatHistoryService.SUPERVISOR_SESSION_ID;
5603
5745
  const rows = this.messageRepo.getBySession(sessionId, limit);
5604
5746
  return rows.reverse().map((row) => {
@@ -5663,7 +5805,7 @@ var ChatHistoryService = class _ChatHistoryService {
5663
5805
  * Gets agent session chat history.
5664
5806
  * Returns messages sorted chronologically (oldest first).
5665
5807
  */
5666
- getAgentHistory(sessionId, limit = 100) {
5808
+ getAgentHistory(sessionId, limit = 20) {
5667
5809
  const rows = this.messageRepo.getBySession(sessionId, limit);
5668
5810
  return rows.reverse().map((row) => {
5669
5811
  let contentBlocks;
@@ -5721,7 +5863,7 @@ var ChatHistoryService = class _ChatHistoryService {
5721
5863
  * @param sessionIds - List of active agent session IDs
5722
5864
  * @param limit - Max messages per session
5723
5865
  */
5724
- getAllAgentHistories(sessionIds, limit = 50) {
5866
+ getAllAgentHistories(sessionIds, limit = 20) {
5725
5867
  const histories = /* @__PURE__ */ new Map();
5726
5868
  for (const sessionId of sessionIds) {
5727
5869
  const history = this.getAgentHistory(sessionId, limit);
@@ -7346,12 +7488,7 @@ var SupervisorAgent = class extends EventEmitter4 {
7346
7488
  if (this.isExecuting && !this.isCancelled) {
7347
7489
  const textBlock = createTextBlock(content);
7348
7490
  this.emit("blocks", deviceId, [textBlock], false);
7349
- const lastTextIndex = allBlocks.findLastIndex((b) => b.block_type === "text");
7350
- if (lastTextIndex >= 0) {
7351
- allBlocks[lastTextIndex] = textBlock;
7352
- } else {
7353
- allBlocks.push(textBlock);
7354
- }
7491
+ accumulateBlocks(allBlocks, [textBlock]);
7355
7492
  }
7356
7493
  } else if (Array.isArray(content)) {
7357
7494
  for (const item of content) {
@@ -7359,7 +7496,7 @@ var SupervisorAgent = class extends EventEmitter4 {
7359
7496
  const block = this.parseContentItem(item);
7360
7497
  if (block) {
7361
7498
  this.emit("blocks", deviceId, [block], false);
7362
- allBlocks.push(block);
7499
+ accumulateBlocks(allBlocks, [block]);
7363
7500
  }
7364
7501
  }
7365
7502
  }
@@ -7368,21 +7505,24 @@ var SupervisorAgent = class extends EventEmitter4 {
7368
7505
  if (lastMessage.getType() === "tool" && this.isExecuting && !this.isCancelled) {
7369
7506
  const toolContent = lastMessage.content;
7370
7507
  const toolName = lastMessage.name ?? "tool";
7508
+ const toolCallId = lastMessage.tool_call_id;
7371
7509
  const toolBlock = createToolBlock(
7372
7510
  toolName,
7373
7511
  "completed",
7374
7512
  void 0,
7375
- typeof toolContent === "string" ? toolContent : JSON.stringify(toolContent)
7513
+ typeof toolContent === "string" ? toolContent : JSON.stringify(toolContent),
7514
+ toolCallId
7376
7515
  );
7377
7516
  this.emit("blocks", deviceId, [toolBlock], false);
7378
- allBlocks.push(toolBlock);
7517
+ accumulateBlocks(allBlocks, [toolBlock]);
7379
7518
  }
7380
7519
  }
7381
7520
  if (this.isExecuting && !this.isCancelled) {
7382
7521
  this.addToHistory("user", command);
7383
7522
  this.addToHistory("assistant", finalOutput);
7523
+ const finalBlocks = mergeToolBlocks(allBlocks);
7384
7524
  const completionBlock = createStatusBlock("Complete");
7385
- this.emit("blocks", deviceId, [completionBlock], true, finalOutput, allBlocks);
7525
+ this.emit("blocks", deviceId, [completionBlock], true, finalOutput, finalBlocks);
7386
7526
  this.logger.debug({ output: finalOutput.slice(0, 200) }, "Supervisor streaming completed");
7387
7527
  } else {
7388
7528
  this.logger.info({ deviceId, isCancelled: this.isCancelled, isExecuting: this.isExecuting }, "Supervisor streaming ended due to cancellation");
@@ -7412,7 +7552,8 @@ var SupervisorAgent = class extends EventEmitter4 {
7412
7552
  if (type === "tool_use") {
7413
7553
  const name = typeof item.name === "string" ? item.name : "tool";
7414
7554
  const input = item.input;
7415
- return createToolBlock(name, "running", input);
7555
+ const toolUseId = typeof item.id === "string" ? item.id : void 0;
7556
+ return createToolBlock(name, "running", input, void 0, toolUseId);
7416
7557
  }
7417
7558
  return null;
7418
7559
  }
@@ -7677,6 +7818,9 @@ Creating worktrees with \`create_worktree\`:
7677
7818
  - When creating sessions, confirm the workspace and project first
7678
7819
  - For ambiguous requests, ask clarifying questions
7679
7820
  - Format responses for terminal display (avoid markdown links)
7821
+ - NEVER use tables - they display poorly on mobile devices
7822
+ - ALWAYS use bullet lists or numbered lists instead of tables
7823
+ - Keep list items short and scannable for mobile reading
7680
7824
  - ALWAYS prioritize safety - check before deleting/merging`;
7681
7825
  return [new HumanMessage(`[System Instructions]
7682
7826
  ${systemPrompt}
@@ -8585,6 +8729,8 @@ var SummarizationService = class {
8585
8729
  buildSystemPrompt() {
8586
8730
  return `You are a concise summarizer for voice output. Summarize AI assistant responses into 1-${this.maxSentences} SHORT sentences.
8587
8731
 
8732
+ CRITICAL: ALWAYS OUTPUT IN ENGLISH. Translate any non-English content to English.
8733
+
8588
8734
  STRICT RULES:
8589
8735
  - Maximum 20 words total (hard limit)
8590
8736
  - Prefer 1 sentence when possible, 2 only if essential
@@ -8592,6 +8738,7 @@ STRICT RULES:
8592
8738
  - Natural spoken language only
8593
8739
  - Never use markdown, bullets, or formatting
8594
8740
  - Never start with "I" - use passive voice or action verbs
8741
+ - ALWAYS translate to English regardless of input language
8595
8742
 
8596
8743
  ABSOLUTELY FORBIDDEN (never include these in output):
8597
8744
  - Session IDs or any alphanumeric identifiers (e.g., "session-abc123", "id: 7f3a2b", UUIDs)
@@ -8599,6 +8746,7 @@ ABSOLUTELY FORBIDDEN (never include these in output):
8599
8746
  - Any string that looks like a technical identifier, hash, or token
8600
8747
  - Code snippets, variable names, or technical jargon
8601
8748
  - Long lists or enumerations
8749
+ - Non-English output (always translate to English)
8602
8750
 
8603
8751
  If the original text contains session IDs or paths, OMIT them entirely. Just describe what happened.
8604
8752
 
@@ -8611,7 +8759,9 @@ GOOD examples:
8611
8759
  BAD examples (never do this):
8612
8760
  - "Created session abc-123-def in /Users/roman/work/project" \u274C
8613
8761
  - "Session ID is 7f3a2b1c" \u274C
8614
- - "Working in /home/user/documents/code" \u274C`;
8762
+ - "Working in /home/user/documents/code" \u274C
8763
+ - "\u0421\u043E\u0437\u0434\u0430\u043D\u0430 \u043D\u043E\u0432\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F Claude." \u274C (non-English)
8764
+ - "Sesi\xF3n creada exitosamente." \u274C (non-English)`;
8615
8765
  }
8616
8766
  /**
8617
8767
  * Builds the user prompt with the text to summarize and optional context.
@@ -9113,22 +9263,29 @@ async function bootstrap() {
9113
9263
  ).map((s) => s.session_id);
9114
9264
  const agentHistoriesMap = chatHistoryService.getAllAgentHistories(agentSessionIds);
9115
9265
  const agentHistories = {};
9116
- for (const [sessionId, history] of agentHistoriesMap) {
9117
- agentHistories[sessionId] = await Promise.all(
9118
- history.map(async (msg) => ({
9119
- sequence: msg.sequence,
9120
- role: msg.role,
9121
- content: msg.content,
9122
- content_blocks: await chatHistoryService.enrichBlocksWithAudio(
9123
- msg.contentBlocks,
9124
- msg.audioOutputPath,
9125
- msg.audioInputPath,
9126
- false
9127
- // Don't include audio in sync.state
9128
- ),
9129
- createdAt: msg.createdAt.toISOString()
9130
- }))
9131
- );
9266
+ const historyEntries = Array.from(agentHistoriesMap.entries());
9267
+ const processedHistories = await Promise.all(
9268
+ historyEntries.map(async ([sessionId, history]) => {
9269
+ const enrichedHistory = await Promise.all(
9270
+ history.map(async (msg) => ({
9271
+ sequence: msg.sequence,
9272
+ role: msg.role,
9273
+ content: msg.content,
9274
+ content_blocks: await chatHistoryService.enrichBlocksWithAudio(
9275
+ msg.contentBlocks,
9276
+ msg.audioOutputPath,
9277
+ msg.audioInputPath,
9278
+ false
9279
+ // Don't include audio in sync.state
9280
+ ),
9281
+ createdAt: msg.createdAt.toISOString()
9282
+ }))
9283
+ );
9284
+ return { sessionId, history: enrichedHistory };
9285
+ })
9286
+ );
9287
+ for (const { sessionId, history } of processedHistories) {
9288
+ agentHistories[sessionId] = history;
9132
9289
  }
9133
9290
  const availableAgentsMap = getAvailableAgents();
9134
9291
  const availableAgents = Array.from(availableAgentsMap.values()).map(
@@ -9262,6 +9419,21 @@ async function bootstrap() {
9262
9419
  }
9263
9420
  let commandText;
9264
9421
  const messageId = commandMessage.payload.message_id;
9422
+ if (messageBroadcaster) {
9423
+ const ackMessageId = messageId || commandMessage.id;
9424
+ const ackMessage = {
9425
+ type: "message.ack",
9426
+ payload: {
9427
+ message_id: ackMessageId,
9428
+ status: "received"
9429
+ }
9430
+ };
9431
+ messageBroadcaster.sendToClient(deviceId, JSON.stringify(ackMessage));
9432
+ logger.debug(
9433
+ { messageId: ackMessageId, deviceId },
9434
+ "Sent message.ack for supervisor.command"
9435
+ );
9436
+ }
9265
9437
  supervisorAgent.resetCancellationState();
9266
9438
  let abortController;
9267
9439
  if (commandMessage.payload.audio) {
@@ -9734,6 +9906,22 @@ async function bootstrap() {
9734
9906
  const deviceId = execMessage.device_id;
9735
9907
  const sessionId = execMessage.session_id;
9736
9908
  const messageId = execMessage.payload.message_id;
9909
+ if (deviceId && messageBroadcaster) {
9910
+ const ackMessageId = messageId || execMessage.id;
9911
+ const ackMessage = {
9912
+ type: "message.ack",
9913
+ payload: {
9914
+ message_id: ackMessageId,
9915
+ session_id: sessionId,
9916
+ status: "received"
9917
+ }
9918
+ };
9919
+ messageBroadcaster.sendToClient(deviceId, JSON.stringify(ackMessage));
9920
+ logger.debug(
9921
+ { messageId: ackMessageId, sessionId, deviceId },
9922
+ "Sent message.ack for session.execute"
9923
+ );
9924
+ }
9737
9925
  cancelledDuringTranscription.delete(sessionId);
9738
9926
  if (execMessage.payload.audio) {
9739
9927
  logger.info(
@@ -10496,7 +10684,7 @@ async function bootstrap() {
10496
10684
  if (persistableBlocks.length > 0) {
10497
10685
  const accumulated = agentMessageAccumulator.get(sessionId) ?? [];
10498
10686
  const wasEmpty = accumulated.length === 0;
10499
- accumulated.push(...persistableBlocks);
10687
+ accumulateBlocks(accumulated, persistableBlocks);
10500
10688
  agentMessageAccumulator.set(sessionId, accumulated);
10501
10689
  if (wasEmpty) {
10502
10690
  logger.debug(
@@ -10551,7 +10739,8 @@ async function bootstrap() {
10551
10739
  }
10552
10740
  }
10553
10741
  const accumulatedBlocks = agentMessageAccumulator.get(sessionId) ?? [];
10554
- const fullAccumulatedText = accumulatedBlocks.filter((b) => b.block_type === "text").map((b) => b.content).join("\n");
10742
+ const mergedBlocks = mergeToolBlocks(accumulatedBlocks);
10743
+ const fullAccumulatedText = mergedBlocks.filter((b) => b.block_type === "text").map((b) => b.content).join("\n");
10555
10744
  const outputEvent = {
10556
10745
  type: "session.output",
10557
10746
  session_id: sessionId,
@@ -10559,8 +10748,8 @@ async function bootstrap() {
10559
10748
  content_type: "agent",
10560
10749
  content: fullAccumulatedText,
10561
10750
  // Full accumulated text for backward compat
10562
- content_blocks: accumulatedBlocks,
10563
- // Full accumulated blocks for rich UI
10751
+ content_blocks: mergedBlocks,
10752
+ // Full accumulated blocks for rich UI (merged)
10564
10753
  timestamp: Date.now(),
10565
10754
  is_complete: isComplete
10566
10755
  }
@@ -10644,10 +10833,11 @@ async function bootstrap() {
10644
10833
  }
10645
10834
  }
10646
10835
  );
10836
+ let supervisorBlockAccumulator = [];
10647
10837
  supervisorAgent.on(
10648
10838
  "blocks",
10649
10839
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
10650
- async (deviceId, blocks, isComplete, finalOutput, allBlocks) => {
10840
+ async (deviceId, blocks, isComplete, finalOutput, _allBlocks) => {
10651
10841
  const wasCancelled = supervisorAgent.wasCancelled();
10652
10842
  logger.debug(
10653
10843
  { deviceId, blockCount: blocks.length, isComplete, wasCancelled },
@@ -10658,26 +10848,35 @@ async function bootstrap() {
10658
10848
  { deviceId, blockCount: blocks.length, isComplete },
10659
10849
  "Ignoring supervisor blocks - execution was cancelled"
10660
10850
  );
10851
+ supervisorBlockAccumulator = [];
10661
10852
  return;
10662
10853
  }
10663
- const textContent = blocks.filter((b) => b.block_type === "text").map((b) => b.content).join("\n");
10854
+ const persistableBlocks = blocks.filter((b) => b.block_type !== "status");
10855
+ if (persistableBlocks.length > 0) {
10856
+ accumulateBlocks(supervisorBlockAccumulator, persistableBlocks);
10857
+ }
10858
+ const mergedBlocks = mergeToolBlocks(supervisorBlockAccumulator);
10859
+ const textContent = mergedBlocks.filter((b) => b.block_type === "text").map((b) => b.content).join("\n");
10664
10860
  const outputEvent = {
10665
10861
  type: "supervisor.output",
10666
10862
  payload: {
10667
10863
  content_type: "supervisor",
10668
10864
  content: textContent,
10669
- content_blocks: blocks,
10865
+ content_blocks: mergedBlocks,
10670
10866
  timestamp: Date.now(),
10671
10867
  is_complete: isComplete
10672
10868
  }
10673
10869
  };
10674
10870
  const message = JSON.stringify(outputEvent);
10675
10871
  broadcaster.broadcastToAll(message);
10872
+ if (isComplete) {
10873
+ supervisorBlockAccumulator = [];
10874
+ }
10676
10875
  if (isComplete && finalOutput && finalOutput.length > 0) {
10677
10876
  chatHistoryService.saveSupervisorMessage(
10678
10877
  "assistant",
10679
10878
  finalOutput,
10680
- allBlocks
10879
+ mergedBlocks
10681
10880
  );
10682
10881
  const pendingVoiceCommand = pendingSupervisorVoiceCommands.get(deviceId);
10683
10882
  if (pendingVoiceCommand && ttsService) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiflis-io/tiflis-code-workstation",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
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",