@tiflis-io/tiflis-code-workstation 0.3.7 → 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.
- package/dist/main.js +394 -126
- 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 (
|
|
251
|
-
PING_INTERVAL_MS:
|
|
252
|
-
/** Max time to wait for pong before considering connection stale (
|
|
253
|
-
PONG_TIMEOUT_MS:
|
|
254
|
-
/** Max time to wait for registration response (
|
|
255
|
-
REGISTRATION_TIMEOUT_MS:
|
|
256
|
-
/** Minimum reconnect delay (
|
|
257
|
-
RECONNECT_DELAY_MIN_MS:
|
|
258
|
-
/** Maximum reconnect delay (
|
|
259
|
-
RECONNECT_DELAY_MAX_MS:
|
|
260
|
-
/** Interval for checking timed-out client connections (
|
|
261
|
-
CLIENT_TIMEOUT_CHECK_INTERVAL_MS:
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
}
|
|
@@ -7508,107 +7649,179 @@ var SupervisorAgent = class extends EventEmitter4 {
|
|
|
7508
7649
|
* Builds the system message for the agent.
|
|
7509
7650
|
*/
|
|
7510
7651
|
buildSystemMessage() {
|
|
7511
|
-
const systemPrompt =
|
|
7512
|
-
|
|
7513
|
-
Your role is to help users:
|
|
7514
|
-
1. **Discover workspaces and projects** - List available workspaces and projects
|
|
7515
|
-
2. **Manage git worktrees** - Create, list, and remove worktrees for parallel development
|
|
7516
|
-
3. **Manage sessions** - Create and terminate agent sessions (Cursor, Claude, OpenCode) and terminal sessions
|
|
7517
|
-
4. **Navigate the file system** - List directories and read files
|
|
7518
|
-
5. **Complete feature workflows** - Merge branches, clean up worktrees, and manage related sessions
|
|
7652
|
+
const systemPrompt = `## MANDATORY RULES (STRICTLY ENFORCED)
|
|
7519
7653
|
|
|
7520
|
-
|
|
7654
|
+
You MUST always respond in English.
|
|
7521
7655
|
|
|
7522
|
-
|
|
7656
|
+
You MUST ALWAYS call tools to execute user requests. You MUST NEVER skip actions based on memory or previous context.
|
|
7523
7657
|
|
|
7524
|
-
###
|
|
7658
|
+
### Tool Usage Requirements:
|
|
7525
7659
|
|
|
7526
|
-
1.
|
|
7527
|
-
- ALWAYS call
|
|
7660
|
+
1. You MUST call tools for fresh data on EVERY request:
|
|
7661
|
+
- ALWAYS call list_workspaces, list_projects, list_sessions, etc. when asked
|
|
7528
7662
|
- NEVER respond from memory or previous conversation context
|
|
7529
|
-
- System state changes constantly -
|
|
7663
|
+
- System state changes constantly - previous data is stale
|
|
7530
7664
|
|
|
7531
|
-
2.
|
|
7665
|
+
2. You MUST execute requested actions immediately:
|
|
7532
7666
|
- ALWAYS call the tool to perform the action, even if you think it was done before
|
|
7533
|
-
- If user asks to create a session and one already exists, CREATE ANOTHER ONE
|
|
7534
|
-
- If user asks to list projects, LIST THEM NOW with a tool call
|
|
7535
|
-
- NEVER refuse a direct request because "it was already done"
|
|
7536
|
-
|
|
7537
|
-
3.
|
|
7538
|
-
- Execute
|
|
7539
|
-
-
|
|
7540
|
-
- Multiple sessions in the same project is
|
|
7667
|
+
- If user asks to create a session and one already exists, CREATE ANOTHER ONE
|
|
7668
|
+
- If user asks to list projects, LIST THEM NOW with a tool call
|
|
7669
|
+
- NEVER refuse a direct request because "it was already done"
|
|
7670
|
+
|
|
7671
|
+
3. User intent is paramount:
|
|
7672
|
+
- Execute requests immediately without questioning
|
|
7673
|
+
- Do NOT assume user made a mistake
|
|
7674
|
+
- Multiple sessions in the same project is valid
|
|
7541
7675
|
- Refreshing information is always valid
|
|
7542
7676
|
|
|
7543
|
-
4.
|
|
7544
|
-
- Call list_workspaces/list_projects EVERY time user asks
|
|
7677
|
+
4. Required tool calls:
|
|
7678
|
+
- Call list_workspaces/list_projects EVERY time user asks about workspaces/projects
|
|
7545
7679
|
- Call list_sessions EVERY time user asks about active sessions
|
|
7680
|
+
- Call list_available_agents BEFORE creating any agent session - NEVER skip this step
|
|
7546
7681
|
- Call create_agent_session/create_terminal_session EVERY time user asks to create a session
|
|
7547
|
-
-
|
|
7682
|
+
- NEVER say "based on our previous conversation" for factual data
|
|
7548
7683
|
|
|
7549
|
-
|
|
7684
|
+
5. Agent selection (CRITICAL):
|
|
7685
|
+
- You MUST call list_available_agents BEFORE creating any agent session
|
|
7686
|
+
- Match user's requested agent name EXACTLY to available agents/aliases
|
|
7687
|
+
- If user says "open zai", use "zai" - do NOT substitute with base type like "claude"
|
|
7688
|
+
- If user request is ambiguous, show available agents and ask for clarification
|
|
7689
|
+
- NEVER assume which agent to use without checking list_available_agents first
|
|
7550
7690
|
|
|
7551
|
-
|
|
7691
|
+
---
|
|
7552
7692
|
|
|
7553
|
-
|
|
7554
|
-
1. **Check branch status** with \`branch_status\` - Look for uncommitted changes
|
|
7555
|
-
2. **List active sessions** with \`get_worktree_session_summary\` - Find sessions in the worktree
|
|
7556
|
-
3. **Ask for confirmation** if there are uncommitted changes or active sessions
|
|
7693
|
+
## YOUR ROLE
|
|
7557
7694
|
|
|
7558
|
-
|
|
7695
|
+
You are the Supervisor Agent for Tiflis Code, a workstation management system.
|
|
7696
|
+
|
|
7697
|
+
Your responsibilities:
|
|
7698
|
+
1. Discover workspaces and projects - List available workspaces and projects
|
|
7699
|
+
2. Manage git worktrees - Create, list, and remove worktrees for parallel development
|
|
7700
|
+
3. Manage sessions - Create and terminate agent sessions (Cursor, Claude, OpenCode) and terminal sessions
|
|
7701
|
+
4. Navigate the file system - List directories and read files
|
|
7702
|
+
5. Complete feature workflows - Merge branches, clean up worktrees, and manage related sessions
|
|
7703
|
+
|
|
7704
|
+
---
|
|
7705
|
+
|
|
7706
|
+
## FEATURE COMPLETION WORKFLOW
|
|
7707
|
+
|
|
7708
|
+
When users ask to "complete the feature", "finish the work", or "merge and clean up":
|
|
7709
|
+
|
|
7710
|
+
Step 1: Check branch status with \`branch_status\` - Look for uncommitted changes
|
|
7711
|
+
Step 2: List active sessions with \`get_worktree_session_summary\` - Find sessions in the worktree
|
|
7712
|
+
Step 3: Ask for confirmation if there are uncommitted changes or active sessions
|
|
7713
|
+
|
|
7714
|
+
### Complete Workflow Tool:
|
|
7715
|
+
Use \`complete_feature\` for one-command solution:
|
|
7559
7716
|
- Merges feature branch into main with automatic push
|
|
7560
7717
|
- Cleans up the worktree and removes the branch if merged
|
|
7561
|
-
- One-command solution for feature completion
|
|
7562
7718
|
|
|
7563
7719
|
### Step-by-Step Alternative:
|
|
7564
|
-
1
|
|
7565
|
-
2
|
|
7566
|
-
3
|
|
7567
|
-
4
|
|
7720
|
+
Step 1: Handle uncommitted changes - Commit, stash, or get user confirmation
|
|
7721
|
+
Step 2: Terminate sessions - Use \`terminate_worktree_sessions\` to clean up active sessions
|
|
7722
|
+
Step 3: Merge branch - Use \`merge_branch\` with pushAfter=true
|
|
7723
|
+
Step 4: Cleanup worktree - Use \`cleanup_worktree\` to remove worktree directory
|
|
7568
7724
|
|
|
7569
7725
|
### Available Merge Tools:
|
|
7570
|
-
-
|
|
7571
|
-
-
|
|
7572
|
-
-
|
|
7573
|
-
-
|
|
7574
|
-
-
|
|
7575
|
-
-
|
|
7576
|
-
-
|
|
7726
|
+
- branch_status: Check current branch state and uncommitted changes
|
|
7727
|
+
- merge_branch: Safe merge with conflict detection and push
|
|
7728
|
+
- complete_feature: Full workflow (merge + cleanup + push)
|
|
7729
|
+
- cleanup_worktree: Remove worktree and delete merged branch
|
|
7730
|
+
- list_mergeable_branches: Show all branches and their cleanup eligibility
|
|
7731
|
+
- get_worktree_session_summary: List sessions in a specific worktree
|
|
7732
|
+
- terminate_worktree_sessions: End all sessions in a worktree
|
|
7577
7733
|
|
|
7578
7734
|
### Error Handling:
|
|
7579
|
-
-
|
|
7580
|
-
-
|
|
7581
|
-
-
|
|
7582
|
-
-
|
|
7735
|
+
- Merge conflicts: Report conflicting files and suggest manual resolution
|
|
7736
|
+
- Uncommitted changes: Offer to commit, stash, or force cleanup
|
|
7737
|
+
- Active sessions: List sessions and ask for termination confirmation
|
|
7738
|
+
- Failed pushes: Continue with local merge, warn about remote sync
|
|
7583
7739
|
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7740
|
+
---
|
|
7741
|
+
|
|
7742
|
+
## AGENT SELECTION (CRITICAL - FOLLOW STRICTLY)
|
|
7743
|
+
|
|
7744
|
+
When user asks to "open an agent", "start an agent", "create a session", or mentions any agent by name:
|
|
7745
|
+
|
|
7746
|
+
Step 1: You MUST call \`list_available_agents\` FIRST to get the current list of available agents and aliases
|
|
7747
|
+
Step 2: Match user intent to the correct agent from the list
|
|
7748
|
+
Step 3: Call \`create_agent_session\` with the exact agent name from the list
|
|
7749
|
+
|
|
7750
|
+
### Agent Matching Rules:
|
|
7591
7751
|
|
|
7592
|
-
|
|
7593
|
-
-
|
|
7594
|
-
-
|
|
7595
|
-
-
|
|
7596
|
-
- **terminal** - Shell terminal for direct commands
|
|
7752
|
+
1. If user mentions a specific name (e.g., "open zai", "start claude", "use cursor"):
|
|
7753
|
+
- Find the EXACT match in the available agents list
|
|
7754
|
+
- If "zai" is an alias, use "zai" - do NOT substitute with the base type
|
|
7755
|
+
- If no exact match, suggest available options
|
|
7597
7756
|
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
-
|
|
7601
|
-
-
|
|
7602
|
-
|
|
7757
|
+
2. If user asks generically (e.g., "open an agent", "start a coding agent"):
|
|
7758
|
+
- Call \`list_available_agents\` and present the options
|
|
7759
|
+
- Ask user which agent they want to use
|
|
7760
|
+
- Do NOT pick the first one or make assumptions
|
|
7761
|
+
|
|
7762
|
+
3. If user mentions a capability (e.g., "I need help with code review"):
|
|
7763
|
+
- Call \`list_available_agents\` to see descriptions
|
|
7764
|
+
- Match the capability to the agent description
|
|
7765
|
+
- If multiple agents match, ask user to choose
|
|
7766
|
+
|
|
7767
|
+
4. NEVER skip \`list_available_agents\`:
|
|
7768
|
+
- Agent aliases are configured via environment variables
|
|
7769
|
+
- The list changes based on workstation configuration
|
|
7770
|
+
- You MUST always check what's actually available
|
|
7771
|
+
|
|
7772
|
+
### Example Flow:
|
|
7773
|
+
User: "open zai on tiflis-code"
|
|
7774
|
+
Step 1: Call list_available_agents -> Returns: claude, cursor, opencode, zai (alias for claude)
|
|
7775
|
+
Step 2: User said "zai" -> Match found: "zai"
|
|
7776
|
+
Step 3: Call create_agent_session with agentName="zai"
|
|
7777
|
+
|
|
7778
|
+
---
|
|
7779
|
+
|
|
7780
|
+
## SESSION TYPES
|
|
7781
|
+
|
|
7782
|
+
Base agent types:
|
|
7783
|
+
- cursor: Cursor AI agent for code assistance
|
|
7784
|
+
- claude: Claude Code CLI for AI coding
|
|
7785
|
+
- opencode: OpenCode AI agent
|
|
7786
|
+
- terminal: Shell terminal for direct commands
|
|
7787
|
+
|
|
7788
|
+
Custom aliases: Configured via AGENT_ALIAS_* environment variables. Always call \`list_available_agents\` to see current aliases.
|
|
7789
|
+
|
|
7790
|
+
### Creating Agent Sessions:
|
|
7791
|
+
Default: Omit the \`worktree\` parameter to create session on the main/master branch (project root directory)
|
|
7792
|
+
Specific worktree: Only specify \`worktree\` when user explicitly asks for a feature branch worktree (NOT the main branch)
|
|
7793
|
+
IMPORTANT: When \`list_worktrees\` shows a worktree named "main" with \`isMain: true\`, this represents the project root directory. Do NOT pass \`worktree: "main"\` - omit the worktree parameter entirely.
|
|
7794
|
+
|
|
7795
|
+
---
|
|
7796
|
+
|
|
7797
|
+
## WORKTREE MANAGEMENT
|
|
7603
7798
|
|
|
7604
|
-
## Worktree Management:
|
|
7605
7799
|
Worktrees allow working on multiple branches simultaneously in separate directories.
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7800
|
+
|
|
7801
|
+
Branch naming: Use conventional format \`<type>/<name>\` where \`<name>\` is lower-kebab-case
|
|
7802
|
+
Types: feature, fix, refactor, docs, chore
|
|
7803
|
+
Examples: feature/user-auth, fix/keyboard-layout, refactor/websocket-handler
|
|
7804
|
+
|
|
7805
|
+
Directory pattern: project--branch-name (slashes replaced with dashes, e.g., my-app--feature-user-auth)
|
|
7806
|
+
|
|
7807
|
+
Creating worktrees with \`create_worktree\`:
|
|
7808
|
+
- createNewBranch: true - Creates a NEW branch and worktree (most common for new features)
|
|
7809
|
+
- createNewBranch: false - Checks out an EXISTING branch into a worktree
|
|
7810
|
+
- baseBranch: Optional starting point for new branches (defaults to HEAD, commonly "main")
|
|
7811
|
+
|
|
7812
|
+
---
|
|
7813
|
+
|
|
7814
|
+
## OUTPUT GUIDELINES
|
|
7815
|
+
|
|
7816
|
+
- Be concise and helpful
|
|
7817
|
+
- Use tools to gather information before responding
|
|
7818
|
+
- When creating sessions, confirm the workspace and project first
|
|
7819
|
+
- For ambiguous requests, ask clarifying questions
|
|
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
|
|
7824
|
+
- ALWAYS prioritize safety - check before deleting/merging`;
|
|
7612
7825
|
return [new HumanMessage(`[System Instructions]
|
|
7613
7826
|
${systemPrompt}
|
|
7614
7827
|
[End Instructions]`)];
|
|
@@ -8516,6 +8729,8 @@ var SummarizationService = class {
|
|
|
8516
8729
|
buildSystemPrompt() {
|
|
8517
8730
|
return `You are a concise summarizer for voice output. Summarize AI assistant responses into 1-${this.maxSentences} SHORT sentences.
|
|
8518
8731
|
|
|
8732
|
+
CRITICAL: ALWAYS OUTPUT IN ENGLISH. Translate any non-English content to English.
|
|
8733
|
+
|
|
8519
8734
|
STRICT RULES:
|
|
8520
8735
|
- Maximum 20 words total (hard limit)
|
|
8521
8736
|
- Prefer 1 sentence when possible, 2 only if essential
|
|
@@ -8523,6 +8738,7 @@ STRICT RULES:
|
|
|
8523
8738
|
- Natural spoken language only
|
|
8524
8739
|
- Never use markdown, bullets, or formatting
|
|
8525
8740
|
- Never start with "I" - use passive voice or action verbs
|
|
8741
|
+
- ALWAYS translate to English regardless of input language
|
|
8526
8742
|
|
|
8527
8743
|
ABSOLUTELY FORBIDDEN (never include these in output):
|
|
8528
8744
|
- Session IDs or any alphanumeric identifiers (e.g., "session-abc123", "id: 7f3a2b", UUIDs)
|
|
@@ -8530,6 +8746,7 @@ ABSOLUTELY FORBIDDEN (never include these in output):
|
|
|
8530
8746
|
- Any string that looks like a technical identifier, hash, or token
|
|
8531
8747
|
- Code snippets, variable names, or technical jargon
|
|
8532
8748
|
- Long lists or enumerations
|
|
8749
|
+
- Non-English output (always translate to English)
|
|
8533
8750
|
|
|
8534
8751
|
If the original text contains session IDs or paths, OMIT them entirely. Just describe what happened.
|
|
8535
8752
|
|
|
@@ -8542,7 +8759,9 @@ GOOD examples:
|
|
|
8542
8759
|
BAD examples (never do this):
|
|
8543
8760
|
- "Created session abc-123-def in /Users/roman/work/project" \u274C
|
|
8544
8761
|
- "Session ID is 7f3a2b1c" \u274C
|
|
8545
|
-
- "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)`;
|
|
8546
8765
|
}
|
|
8547
8766
|
/**
|
|
8548
8767
|
* Builds the user prompt with the text to summarize and optional context.
|
|
@@ -9044,22 +9263,29 @@ async function bootstrap() {
|
|
|
9044
9263
|
).map((s) => s.session_id);
|
|
9045
9264
|
const agentHistoriesMap = chatHistoryService.getAllAgentHistories(agentSessionIds);
|
|
9046
9265
|
const agentHistories = {};
|
|
9047
|
-
|
|
9048
|
-
|
|
9049
|
-
|
|
9050
|
-
|
|
9051
|
-
|
|
9052
|
-
|
|
9053
|
-
|
|
9054
|
-
msg.
|
|
9055
|
-
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
|
|
9059
|
-
|
|
9060
|
-
|
|
9061
|
-
|
|
9062
|
-
|
|
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;
|
|
9063
9289
|
}
|
|
9064
9290
|
const availableAgentsMap = getAvailableAgents();
|
|
9065
9291
|
const availableAgents = Array.from(availableAgentsMap.values()).map(
|
|
@@ -9193,6 +9419,21 @@ async function bootstrap() {
|
|
|
9193
9419
|
}
|
|
9194
9420
|
let commandText;
|
|
9195
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
|
+
}
|
|
9196
9437
|
supervisorAgent.resetCancellationState();
|
|
9197
9438
|
let abortController;
|
|
9198
9439
|
if (commandMessage.payload.audio) {
|
|
@@ -9665,6 +9906,22 @@ async function bootstrap() {
|
|
|
9665
9906
|
const deviceId = execMessage.device_id;
|
|
9666
9907
|
const sessionId = execMessage.session_id;
|
|
9667
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
|
+
}
|
|
9668
9925
|
cancelledDuringTranscription.delete(sessionId);
|
|
9669
9926
|
if (execMessage.payload.audio) {
|
|
9670
9927
|
logger.info(
|
|
@@ -10427,7 +10684,7 @@ async function bootstrap() {
|
|
|
10427
10684
|
if (persistableBlocks.length > 0) {
|
|
10428
10685
|
const accumulated = agentMessageAccumulator.get(sessionId) ?? [];
|
|
10429
10686
|
const wasEmpty = accumulated.length === 0;
|
|
10430
|
-
accumulated
|
|
10687
|
+
accumulateBlocks(accumulated, persistableBlocks);
|
|
10431
10688
|
agentMessageAccumulator.set(sessionId, accumulated);
|
|
10432
10689
|
if (wasEmpty) {
|
|
10433
10690
|
logger.debug(
|
|
@@ -10482,7 +10739,8 @@ async function bootstrap() {
|
|
|
10482
10739
|
}
|
|
10483
10740
|
}
|
|
10484
10741
|
const accumulatedBlocks = agentMessageAccumulator.get(sessionId) ?? [];
|
|
10485
|
-
const
|
|
10742
|
+
const mergedBlocks = mergeToolBlocks(accumulatedBlocks);
|
|
10743
|
+
const fullAccumulatedText = mergedBlocks.filter((b) => b.block_type === "text").map((b) => b.content).join("\n");
|
|
10486
10744
|
const outputEvent = {
|
|
10487
10745
|
type: "session.output",
|
|
10488
10746
|
session_id: sessionId,
|
|
@@ -10490,8 +10748,8 @@ async function bootstrap() {
|
|
|
10490
10748
|
content_type: "agent",
|
|
10491
10749
|
content: fullAccumulatedText,
|
|
10492
10750
|
// Full accumulated text for backward compat
|
|
10493
|
-
content_blocks:
|
|
10494
|
-
// Full accumulated blocks for rich UI
|
|
10751
|
+
content_blocks: mergedBlocks,
|
|
10752
|
+
// Full accumulated blocks for rich UI (merged)
|
|
10495
10753
|
timestamp: Date.now(),
|
|
10496
10754
|
is_complete: isComplete
|
|
10497
10755
|
}
|
|
@@ -10575,10 +10833,11 @@ async function bootstrap() {
|
|
|
10575
10833
|
}
|
|
10576
10834
|
}
|
|
10577
10835
|
);
|
|
10836
|
+
let supervisorBlockAccumulator = [];
|
|
10578
10837
|
supervisorAgent.on(
|
|
10579
10838
|
"blocks",
|
|
10580
10839
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
10581
|
-
async (deviceId, blocks, isComplete, finalOutput,
|
|
10840
|
+
async (deviceId, blocks, isComplete, finalOutput, _allBlocks) => {
|
|
10582
10841
|
const wasCancelled = supervisorAgent.wasCancelled();
|
|
10583
10842
|
logger.debug(
|
|
10584
10843
|
{ deviceId, blockCount: blocks.length, isComplete, wasCancelled },
|
|
@@ -10589,26 +10848,35 @@ async function bootstrap() {
|
|
|
10589
10848
|
{ deviceId, blockCount: blocks.length, isComplete },
|
|
10590
10849
|
"Ignoring supervisor blocks - execution was cancelled"
|
|
10591
10850
|
);
|
|
10851
|
+
supervisorBlockAccumulator = [];
|
|
10592
10852
|
return;
|
|
10593
10853
|
}
|
|
10594
|
-
const
|
|
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");
|
|
10595
10860
|
const outputEvent = {
|
|
10596
10861
|
type: "supervisor.output",
|
|
10597
10862
|
payload: {
|
|
10598
10863
|
content_type: "supervisor",
|
|
10599
10864
|
content: textContent,
|
|
10600
|
-
content_blocks:
|
|
10865
|
+
content_blocks: mergedBlocks,
|
|
10601
10866
|
timestamp: Date.now(),
|
|
10602
10867
|
is_complete: isComplete
|
|
10603
10868
|
}
|
|
10604
10869
|
};
|
|
10605
10870
|
const message = JSON.stringify(outputEvent);
|
|
10606
10871
|
broadcaster.broadcastToAll(message);
|
|
10872
|
+
if (isComplete) {
|
|
10873
|
+
supervisorBlockAccumulator = [];
|
|
10874
|
+
}
|
|
10607
10875
|
if (isComplete && finalOutput && finalOutput.length > 0) {
|
|
10608
10876
|
chatHistoryService.saveSupervisorMessage(
|
|
10609
10877
|
"assistant",
|
|
10610
10878
|
finalOutput,
|
|
10611
|
-
|
|
10879
|
+
mergedBlocks
|
|
10612
10880
|
);
|
|
10613
10881
|
const pendingVoiceCommand = pendingSupervisorVoiceCommands.get(deviceId);
|
|
10614
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.
|
|
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",
|