@kendoo.agentdesk/agentdesk 0.9.1 → 0.9.3

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/cli/config.mjs CHANGED
@@ -75,10 +75,10 @@ export async function loadConfig(dir, opts = {}) {
75
75
  // Fetch server settings
76
76
  const serverConfig = await fetchServerConfig(projectName, apiKey, serverUrl);
77
77
 
78
- // Merge: DEFAULTS ← serverlocal (local overrides server)
78
+ // Merge: DEFAULTS ← localserver (UI is the single source of truth)
79
79
  let config = { ...DEFAULTS };
80
- if (serverConfig) config = deepMerge(config, serverConfig);
81
80
  if (localConfig) config = deepMerge(config, localConfig);
81
+ if (serverConfig) config = deepMerge(config, serverConfig);
82
82
 
83
83
  // Auto-sync: if local config exists but server is empty, push local to server
84
84
  if (localConfig && !serverConfig && apiKey && serverUrl && projectName) {
package/cli/daemon.mjs CHANGED
@@ -313,7 +313,7 @@ export async function runDaemon() {
313
313
 
314
314
  // 4. Session handling
315
315
 
316
- async function handleStartSession({ sessionId, projectId, prompt, mode }) {
316
+ async function handleStartSession({ sessionId, projectId, taskId: remoteTaskId, prompt, mode }) {
317
317
  // Validate project against local allowlist
318
318
  const project = projects.find(p => p.id === projectId);
319
319
  if (!project) {
@@ -345,13 +345,23 @@ export async function runDaemon() {
345
345
  const config = await loadConfig(project.path, { apiKey: projectApiKey, serverUrl: agentdeskServer, projectName: project.name });
346
346
  const tracker = config.tracker || (detected.hasLinear ? "linear" : null);
347
347
 
348
- // Generate task ID
349
- const taskId = prompt
348
+ // Use provided task ID or generate from description
349
+ const taskId = remoteTaskId || (prompt
350
350
  ? prompt.toLowerCase().replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-").slice(0, 40) || `task-${Date.now().toString(36)}`
351
- : `daemon-${Date.now().toString(36)}`;
351
+ : `daemon-${Date.now().toString(36)}`);
352
352
 
353
353
  // Build task link
354
354
  let taskLink = null;
355
+ if (remoteTaskId) {
356
+ if (tracker === "linear" && config.linear?.workspace) {
357
+ taskLink = `https://linear.app/${config.linear.workspace}/issue/${remoteTaskId}`;
358
+ } else if (tracker === "jira" && config.jira?.baseUrl) {
359
+ taskLink = `${config.jira.baseUrl}/browse/${remoteTaskId}`;
360
+ } else if (tracker === "github") {
361
+ const repo = config.github?.repo || "";
362
+ if (repo) taskLink = `https://github.com/${repo}/issues/${remoteTaskId}`;
363
+ }
364
+ }
355
365
 
356
366
  // Resolve team
357
367
  const team = resolveTeam(config);
@@ -364,15 +374,18 @@ export async function runDaemon() {
364
374
  const result = await runOrchestrator({
365
375
  taskId, taskLink,
366
376
  description: prompt || "",
367
- createTask: false,
377
+ createTask: !remoteTaskId && !!prompt && !!tracker,
368
378
  tracker, config,
369
379
  project: detected, team, teamSections,
370
380
  inboxUrl, sessionUrl,
371
381
  cwd: project.path,
372
382
  mode: mode || "full",
383
+ apiKey,
384
+ serverUrl: agentdeskServer,
373
385
  onEvent(event) {
386
+ // Skip events after cancellation to prevent double session:end
387
+ if (!activeSession || activeSession.sessionId !== sessionId) return;
374
388
  sendBuffered(sessionId, event);
375
- // Track file paths for metadata logging
376
389
  if (event.type === "tool:use" && event.description) {
377
390
  const pathMatch = event.description.match(/(?:Reading|Editing|Writing)\s+(.+)/);
378
391
  if (pathMatch) filePathsTouched.add(pathMatch[1]);
@@ -411,8 +424,12 @@ export async function runDaemon() {
411
424
  function handleCancelSession({ sessionId }) {
412
425
  if (activeSession?.sessionId === sessionId) {
413
426
  console.log(` ${yellow}Cancelling session${reset} ${dim}${sessionId}${reset}`);
427
+ const duration = `${((Date.now() - activeSession.startedAt) / 1000).toFixed(1)}s`;
414
428
  killChild(activeSession.child);
429
+ // Clear active session BEFORE sending session:end — this prevents
430
+ // the orchestrator's onEvent callback from sending a duplicate
415
431
  activeSession = null;
432
+ sendBuffered(sessionId, { type: "session:end", duration, steps: 0, inputTokens: 0, outputTokens: 0 });
416
433
  }
417
434
  }
418
435
 
@@ -14,6 +14,19 @@ const CLI_VERSION = JSON.parse(readFileSync(join(__dirname, "../package.json"),
14
14
  import { buildAgentPrompt, getAgentTools } from "./agent-prompts.mjs";
15
15
  import { detectProject, generateContext } from "./detect.mjs";
16
16
 
17
+ // Fetch decrypted tracker credentials from server
18
+ async function fetchTrackerCredentials(projectName, apiKey, serverUrl) {
19
+ if (!apiKey || !serverUrl || !projectName) return {};
20
+ try {
21
+ const res = await fetch(`${serverUrl}/api/projects/${projectName}/settings/credentials`, {
22
+ headers: { "x-api-key": apiKey },
23
+ signal: AbortSignal.timeout(5000),
24
+ });
25
+ if (res.ok) return await res.json();
26
+ } catch {}
27
+ return {};
28
+ }
29
+
17
30
  function loadDotEnv(dir) {
18
31
  const envPath = join(dir, ".env");
19
32
  if (!existsSync(envPath)) return {};
@@ -37,11 +50,11 @@ function timestamp() {
37
50
  export async function runOrchestrator({
38
51
  taskId, taskLink, description, createTask, tracker, config,
39
52
  project, team, teamSections, inboxUrl, sessionUrl, cwd,
40
- onEvent, mode = "full",
53
+ onEvent, mode = "full", apiKey, serverUrl,
41
54
  }) {
42
55
  // --- Lite mode: single process, legacy behavior ---
43
56
  if (mode === "lite") {
44
- return runLiteMode({ taskId, taskLink, description, createTask, tracker, config, project, teamSections, inboxUrl, sessionUrl, cwd, onEvent });
57
+ return runLiteMode({ taskId, taskLink, description, createTask, tracker, config, project, teamSections, inboxUrl, sessionUrl, cwd, onEvent, apiKey, serverUrl });
45
58
  }
46
59
 
47
60
  // --- Full mode: independent sub-agents ---
@@ -54,7 +67,8 @@ export async function runOrchestrator({
54
67
  totalSteps: 0,
55
68
  };
56
69
 
57
- const env = { ...process.env, ...loadDotEnv(cwd) };
70
+ const trackerCreds = await fetchTrackerCredentials(project?.name, apiKey, serverUrl);
71
+ const env = { ...process.env, ...loadDotEnv(cwd), ...trackerCreds };
58
72
 
59
73
  // Emit event to WebSocket (same protocol as single-process mode)
60
74
  function emit(event) {
@@ -303,8 +317,9 @@ export async function runOrchestrator({
303
317
 
304
318
  // --- Lite mode: single Claude process with all agents as personas ---
305
319
 
306
- async function runLiteMode({ taskId, taskLink, description, createTask, tracker, config, project, teamSections, inboxUrl, sessionUrl, cwd, onEvent }) {
307
- const env = { ...process.env, ...loadDotEnv(cwd) };
320
+ async function runLiteMode({ taskId, taskLink, description, createTask, tracker, config, project, teamSections, inboxUrl, sessionUrl, cwd, onEvent, apiKey, serverUrl }) {
321
+ const trackerCreds = await fetchTrackerCredentials(project?.name, apiKey, serverUrl);
322
+ const env = { ...process.env, ...loadDotEnv(cwd), ...trackerCreds };
308
323
  const startTime = Date.now();
309
324
  let totalInputTokens = 0;
310
325
  let totalOutputTokens = 0;
package/cli/team.mjs CHANGED
@@ -177,6 +177,8 @@ export async function runTeam(taskId, opts = {}) {
177
177
  project, team, teamSections, inboxUrl, sessionUrl, cwd,
178
178
  onEvent: vizSend,
179
179
  mode: agentMode,
180
+ apiKey,
181
+ serverUrl: agentdeskServer,
180
182
  });
181
183
 
182
184
  console.log(`\n━━━ DONE ━━━`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kendoo.agentdesk/agentdesk",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "AI team orchestrator for Claude Code — run collaborative agent sessions from your terminal",
5
5
  "type": "module",
6
6
  "bin": {