clideck 1.31.23 → 1.31.24

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.
@@ -0,0 +1,43 @@
1
+ const { binName } = require('./utils');
2
+
3
+ const GUIDE = `CliDeck session guide:
4
+ You are running inside CliDeck only when CLIDECK_SESSION_ID is set.
5
+ Use clideck agents to discover peer sessions.
6
+ Use clideck ask status to see which sessions are idle or busy.
7
+ Use clideck ask "<target>" "<message>" --timeout 10m to ask an idle peer agent.
8
+ Use the exact @project/session address from clideck agents --all for cross-project asks.
9
+ Keep clideck ask running until it exits; the answer returns on stdout.
10
+ If a target is busy, the request is not queued. Try later or ask another idle agent.
11
+ If CLIDECK_SESSION_ID is missing, ignore this guide.`;
12
+
13
+ function commandStart(parts) {
14
+ if (process.platform === 'win32' && parts.length > 2 && /^cmd(?:\.exe)?$/i.test(binName(parts[0])) && parts[1].toLowerCase() === '/c') {
15
+ return 2;
16
+ }
17
+ return 0;
18
+ }
19
+
20
+ function hasCodexDeveloperInstructions(parts) {
21
+ return parts.some((part, idx) => {
22
+ if (part === '-c' || part === '--config') return String(parts[idx + 1] || '').startsWith('developer_instructions=');
23
+ return String(part || '').startsWith('-cdeveloper_instructions=')
24
+ || String(part || '').startsWith('--config=developer_instructions=');
25
+ });
26
+ }
27
+
28
+ function withCliDeckGuide(parts, presetId) {
29
+ const next = [...parts];
30
+ const idx = commandStart(next);
31
+
32
+ if (presetId === 'claude-code') {
33
+ if (next.includes('--system-prompt') || next.includes('--append-system-prompt')) return next;
34
+ next.splice(idx + 1, 0, '--append-system-prompt', GUIDE);
35
+ } else if (presetId === 'codex') {
36
+ if (hasCodexDeveloperInstructions(next)) return next;
37
+ next.splice(idx + 1, 0, '-c', `developer_instructions=${JSON.stringify(GUIDE)}`);
38
+ }
39
+
40
+ return next;
41
+ }
42
+
43
+ module.exports = { withCliDeckGuide, GUIDE };
package/bin/clideck.js CHANGED
@@ -9,6 +9,7 @@ function usage() {
9
9
  'Usage:',
10
10
  ' clideck [--host <host>] [--port <port>]',
11
11
  ' clideck agents [--json] [--all]',
12
+ ' clideck ask status [--json] [--all]',
12
13
  ' clideck ask --session <name-or-id> --message <text> [--timeout 10m]',
13
14
  '',
14
15
  'Options:',
@@ -25,6 +26,8 @@ function usage() {
25
26
  '',
26
27
  ' clideck ask',
27
28
  ' Use from inside a CliDeck session when one agent needs an answer from another session.',
29
+ ' This lets agents communicate with each other without manual copy/paste through the user.',
30
+ ' Use `clideck ask status` first when the agent only needs idle/busy state.',
28
31
  '',
29
32
  'Ask behavior:',
30
33
  ' Unscoped target lookup is limited to the same project as the caller session.',
@@ -45,13 +48,18 @@ function usage() {
45
48
  ' clideck agents',
46
49
  ' clideck agents --json',
47
50
  ' clideck agents --all',
51
+ ' clideck ask status',
52
+ ' clideck ask status --all',
48
53
  ' clideck ask --session "Reviewer" --message "Review my changes and return only findings."',
49
54
  ' clideck ask "research manager" "Check this plan and tell me what is missing." --timeout 15m',
50
55
  ' clideck ask "@website/Docs Writer" "Check if the docs mention the new CLI flags." --timeout 15m',
51
56
  ' cat notes.md | clideck ask --session "Docs Writer" --timeout 10m',
52
57
  '',
53
58
  'Notes for agents:',
59
+ ' CliDeck supports agent-to-agent communication: discover teammates with `clideck agents`,',
60
+ ' then ask them directly with `clideck ask`.',
54
61
  ' Run `clideck agents` to discover available same-project sessions.',
62
+ ' Run `clideck ask status` to check who is idle or busy before dispatching work.',
55
63
  ' Run `clideck agents --all` before a cross-project ask.',
56
64
  ' Run `clideck ask --help` for the exact ask command contract.',
57
65
  ' If a target has spaces, quote the whole target, including @project/session addresses.',
@@ -7,6 +7,7 @@ function usage() {
7
7
  ' clideck agents [--json] [--all]',
8
8
  '',
9
9
  'Lists active CliDeck sessions in the same project as the caller session.',
10
+ 'Use this to discover other agents before communicating with them through `clideck ask`.',
10
11
  'Use this from inside a CliDeck session before `clideck ask` to discover target names.',
11
12
  'Use --all to discover cross-project targets and their @project/session ask addresses.',
12
13
  'Use the printed ask= value exactly when present, and prefer idle targets for `clideck ask`.',
@@ -4,15 +4,18 @@ const https = require('https');
4
4
  function usage() {
5
5
  return [
6
6
  'Usage:',
7
+ ' clideck ask status [--json] [--all]',
7
8
  ' clideck ask --session <name-or-id> --message <text> [--timeout 10m]',
8
9
  ' clideck ask <name-or-id> <message> [--timeout 10m]',
9
10
  ' clideck ask "@project-name/session-name" <message> [--timeout 10m]',
10
11
  ' cat file.txt | clideck ask --session <name-or-id> [--timeout 10m]',
11
12
  '',
12
13
  'Use from inside a CliDeck session when this agent needs an answer from another active session.',
14
+ 'CliDeck ask lets agents communicate with each other without manual copy/paste through the user.',
13
15
  'Unscoped target lookup is limited to the same project as the caller session.',
14
16
  'Use @project/session only when you intentionally need to ask across projects.',
15
17
  'Run `clideck agents` or `clideck agents --all` first to discover available target sessions.',
18
+ 'Run `clideck ask status` to quickly see which project sessions are idle or busy.',
16
19
  'Copy and use the target exactly as listed by `clideck agents`; quote it if it contains spaces.',
17
20
  '',
18
21
  'Important for agents:',
@@ -28,15 +31,32 @@ function usage() {
28
31
  ' To ask multiple agents, start one `clideck ask` command per target and keep each command open.',
29
32
  '',
30
33
  'Options:',
34
+ ' status Show idle/busy state for active project sessions.',
31
35
  ' -s, --session <name-or-id> Target session name or id.',
32
36
  ' -m, --message <text> Message to send. If omitted, stdin is used.',
33
37
  ' -t, --timeout <duration> Wait time. Examples: 30s, 10m, 1h. Default: 10m.',
34
38
  ' --url <url> CliDeck server URL. Default: CLIDECK_URL or local port.',
39
+ ' --json With status: print machine-readable JSON.',
40
+ ' --all With status: include all projects.',
35
41
  ' --no-progress Do not print waiting hints to stderr.',
36
42
  ' -h, --help Show this help.',
37
43
  ].join('\n');
38
44
  }
39
45
 
46
+ function parseStatusArgs(args) {
47
+ const port = process.env.CLIDECK_PORT || process.env.PORT || '4000';
48
+ const out = { json: false, all: false, url: process.env.CLIDECK_URL || `http://127.0.0.1:${port}` };
49
+ for (let i = 0; i < args.length; i++) {
50
+ const arg = args[i];
51
+ if (arg === '--json') out.json = true;
52
+ else if (arg === '--all') out.all = true;
53
+ else if (arg === '--url') out.url = args[++i];
54
+ else if (arg === '--help' || arg === '-h') out.help = true;
55
+ else throw new Error(`Unknown status argument: ${arg}`);
56
+ }
57
+ return out;
58
+ }
59
+
40
60
  function parseDuration(value) {
41
61
  if (!value) return 10 * 60 * 1000;
42
62
  const m = String(value).trim().match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/i);
@@ -133,6 +153,32 @@ function findAgent(agents, target) {
133
153
  return insensitive.length === 1 ? insensitive[0] : null;
134
154
  }
135
155
 
156
+ function formatStatus(agents, opts = {}) {
157
+ if (!agents.length) return opts.all ? 'No active sessions found.' : 'No active sessions found in this project.';
158
+ return agents.map(a => {
159
+ const status = a.working ? 'busy' : 'idle';
160
+ const self = a.caller ? ' self' : '';
161
+ const address = a.address && a.address !== a.name ? ` ${a.address}` : '';
162
+ const project = opts.all && a.project ? ` project="${a.project}"` : '';
163
+ return `${status.padEnd(4)} ${a.name}${self}${address}${project}`;
164
+ }).join('\n');
165
+ }
166
+
167
+ async function runStatus(args) {
168
+ const opts = parseStatusArgs(args);
169
+ if (opts.help) {
170
+ console.log(usage());
171
+ return;
172
+ }
173
+ const callerSessionId = process.env.CLIDECK_SESSION_ID || '';
174
+ if (!callerSessionId) throw new Error('CLIDECK_SESSION_ID is missing. Run this from inside a CliDeck session.');
175
+ const all = opts.all ? '&all=1' : '';
176
+ const res = await getJson(opts.url, `/api/session/agents?callerSessionId=${encodeURIComponent(callerSessionId)}${all}`);
177
+ const agents = (res.agents || []).map(a => ({ ...a, status: a.working ? 'busy' : 'idle' }));
178
+ if (opts.json) process.stdout.write(JSON.stringify(agents, null, 2) + '\n');
179
+ else process.stdout.write(formatStatus(agents, opts) + '\n');
180
+ }
181
+
136
182
  function startProgressHints(opts, callerSessionId) {
137
183
  if (!opts.progress) return () => {};
138
184
  const started = Date.now();
@@ -212,6 +258,10 @@ function postJson(url, payload, timeoutMs) {
212
258
 
213
259
  async function run(args) {
214
260
  try {
261
+ if (args[0] === 'status') {
262
+ await runStatus(args.slice(1));
263
+ return;
264
+ }
215
265
  const opts = parseArgs(args);
216
266
  if (opts.help) {
217
267
  console.log(usage());
@@ -236,4 +286,4 @@ async function run(args) {
236
286
  }
237
287
  }
238
288
 
239
- module.exports = { run, parseArgs, parseDuration };
289
+ module.exports = { run, parseArgs, parseDuration, parseStatusArgs, formatStatus };
package/config.js CHANGED
@@ -47,6 +47,8 @@ const DEFAULTS = {
47
47
  notifySoundEnabled: true,
48
48
  notifySound: 'soft-beep',
49
49
  notifyMinWork: 0,
50
+ askDispatchSoundEnabled: true,
51
+ askDispatchSound: 'agent-dispatch-ambient',
50
52
  defaultTheme: 'catppuccin-mocha',
51
53
  defaultShell,
52
54
  prompts: [],
package/handlers.js CHANGED
@@ -261,11 +261,11 @@ function detectTelemetryConfig(c) {
261
261
  repairAllowed = repairAllowed || hasAnyExistingHook(hooks, 'claude-hook.js');
262
262
  detected = hasExistingHook(hooks.UserPromptSubmit, 'claude-hook.js', 'start')
263
263
  && hasExistingHook(hooks.Stop, 'claude-hook.js', 'stop')
264
- && hasExistingHook(hooks.StopFailure, 'claude-hook.js', 'stop')
265
264
  && hasExistingHook(hooks.SessionStart, 'claude-hook.js', 'session-start')
266
265
  && hasExistingHook(hooks.SessionEnd, 'claude-hook.js', 'session-end')
267
266
  && hasExistingHook(hooks.PreToolUse, 'claude-hook.js', 'menu')
268
- && hooks.Notification?.some(h => h.matcher === 'idle_prompt' && hasExistingHook([h], 'claude-hook.js', 'idle'));
267
+ && hooks.Notification?.some(h => h.matcher === 'idle_prompt' && hasExistingHook([h], 'claude-hook.js', 'idle'))
268
+ && !hooks.StopFailure;
269
269
  if (detected && cmd.telemetrySetupConsent !== true) {
270
270
  cmd.telemetrySetupConsent = true;
271
271
  changed = true;
@@ -765,24 +765,23 @@ function applyTelemetryConfig(preset, cmd = null) {
765
765
  const hasClideck = (arr, path) => arr?.some(h => h.hooks?.some(x => x.command === hookCmd(path)));
766
766
  if (hasClideck(hooks.UserPromptSubmit, 'start')
767
767
  && hasClideck(hooks.Stop, 'stop')
768
- && hasClideck(hooks.StopFailure, 'stop')
769
768
  && hasClideck(hooks.SessionStart, 'session-start')
770
769
  && hasClideck(hooks.SessionEnd, 'session-end')
771
770
  && hasClideck(hooks.PreToolUse, 'menu')
772
- && hooks.Notification?.some(h => h.matcher === 'idle_prompt' && h.hooks?.some(x => x.command === hookCmd('idle')))) {
771
+ && hooks.Notification?.some(h => h.matcher === 'idle_prompt' && h.hooks?.some(x => x.command === hookCmd('idle')))
772
+ && !hooks.StopFailure) {
773
773
  return { success: true, message: 'Already configured' };
774
774
  }
775
775
  const stripOld = (arr) => (arr || []).filter(h => !h.hooks?.some(x => x.url?.includes('/hook/claude/') || x.command?.includes('claude-hook.js')));
776
776
  hooks.UserPromptSubmit = stripOld(hooks.UserPromptSubmit);
777
777
  hooks.Stop = stripOld(hooks.Stop);
778
- hooks.StopFailure = stripOld(hooks.StopFailure);
778
+ delete hooks.StopFailure;
779
779
  hooks.SessionStart = stripOld(hooks.SessionStart);
780
780
  hooks.SessionEnd = stripOld(hooks.SessionEnd);
781
781
  hooks.PreToolUse = stripOld(hooks.PreToolUse);
782
782
  hooks.Notification = stripOld(hooks.Notification);
783
783
  if (!hasClideck(hooks.UserPromptSubmit, 'start')) hooks.UserPromptSubmit = [...(hooks.UserPromptSubmit || []), clideckHook('start')];
784
784
  if (!hasClideck(hooks.Stop, 'stop')) hooks.Stop = [...(hooks.Stop || []), clideckHook('stop')];
785
- if (!hasClideck(hooks.StopFailure, 'stop')) hooks.StopFailure = [...(hooks.StopFailure || []), clideckHook('stop')];
786
785
  if (!hasClideck(hooks.SessionStart, 'session-start')) hooks.SessionStart = [...(hooks.SessionStart || []), clideckHook('session-start')];
787
786
  if (!hasClideck(hooks.SessionEnd, 'session-end')) hooks.SessionEnd = [...(hooks.SessionEnd || []), clideckHook('session-end')];
788
787
  if (!hasClideck(hooks.Notification, 'idle')) hooks.Notification = [...(hooks.Notification || []), { matcher: 'idle_prompt', ...clideckHook('idle') }];
@@ -883,7 +882,8 @@ function removeTelemetryConfig(preset, cmd = null) {
883
882
  let settings = {};
884
883
  try { settings = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
885
884
  if (!settings.hooks) return { success: true, message: 'No hooks to remove' };
886
- for (const event of ['UserPromptSubmit', 'Stop', 'StopFailure', 'SessionStart', 'SessionEnd', 'Notification', 'PreToolUse']) {
885
+ delete settings.hooks.StopFailure;
886
+ for (const event of ['UserPromptSubmit', 'Stop', 'SessionStart', 'SessionEnd', 'Notification', 'PreToolUse']) {
887
887
  const arr = settings.hooks[event];
888
888
  if (!arr) continue;
889
889
  settings.hooks[event] = arr.filter(h => !h.hooks?.some(x => x.url?.includes('/hook/claude/') || x.command?.includes('claude-hook.js')));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clideck",
3
- "version": "1.31.23",
3
+ "version": "1.31.24",
4
4
  "description": "One screen for all your AI coding agents — run, monitor, and manage multiple CLI agents from a single browser tab",
5
5
  "main": "server.js",
6
6
  "bin": {
package/public/index.html CHANGED
@@ -372,6 +372,25 @@
372
372
  </button>
373
373
  </div>
374
374
  </div>
375
+ <div class="mb-5">
376
+ <label class="flex items-center gap-2 text-sm text-slate-300 cursor-pointer">
377
+ <input type="checkbox" id="cfg-ask-dispatch-sound" name="cfg-ask-dispatch-sound" class="accent-blue-500" checked>
378
+ Play sound when an agent dispatches another agent
379
+ </label>
380
+ <p class="text-xs text-slate-500 mt-1.5 ml-6">Soft ambient cue when `clideck ask` sends work into another session</p>
381
+ </div>
382
+ <div id="ask-dispatch-sound-row" class="mb-5 ml-6">
383
+ <label class="block text-xs text-slate-400 mb-1.5">Dispatch sound</label>
384
+ <div class="flex items-center gap-2">
385
+ <select id="cfg-ask-dispatch-sound-pick" class="flex-1 px-3 py-1.5 text-sm bg-slate-800 border border-slate-600 rounded-md text-slate-200 outline-none focus:border-blue-500 transition-colors cursor-pointer">
386
+ <option value="agent-dispatch-ambient">Ambient dispatch</option>
387
+ <option value="agent-dispatch-soft">Soft dispatch</option>
388
+ </select>
389
+ <button id="btn-ask-dispatch-sound-preview" class="flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-md border border-slate-600 text-slate-400 hover:text-slate-200 hover:bg-slate-700 transition-colors" title="Preview dispatch sound">
390
+ <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/></svg>
391
+ </button>
392
+ </div>
393
+ </div>
375
394
  <div class="mb-5">
376
395
  <label class="block text-xs text-slate-400 mb-1.5 ml-6">Minimum working time before notifying</label>
377
396
  <select id="cfg-notify-min-work" class="ml-6 px-3 py-1.5 text-sm bg-slate-800 border border-slate-600 rounded-md text-slate-200 outline-none focus:border-blue-500 transition-colors cursor-pointer">
@@ -384,8 +403,9 @@
384
403
  <p class="text-[11px] font-medium text-slate-400 mb-1.5">When do notifications fire?</p>
385
404
  <div class="text-[11px] text-slate-500 leading-relaxed space-y-1">
386
405
  <p><span class="text-slate-400">Sound</span> — plays for background sessions. If you're viewing the session, no sound.</p>
406
+ <p><span class="text-slate-400">Dispatch</span> — plays when `clideck ask` sends work into another agent.</p>
387
407
  <p><span class="text-slate-400">Browser</span> — only when the CliDeck tab is not in focus.</p>
388
- <p><span class="text-slate-400">Mute</span> — right-click a session to mute both sound and browser notifications.</p>
408
+ <p><span class="text-slate-400">Mute</span> — right-click a session to mute notifications and dispatch cues for it.</p>
389
409
  </div>
390
410
  </div>
391
411
  </div>
package/public/js/app.js CHANGED
@@ -37,6 +37,14 @@ function expandProjectForNewSession(projectId) {
37
37
  send({ type: 'config.update', config: state.cfg });
38
38
  }
39
39
 
40
+ function playAskDispatchSound(msg) {
41
+ if (state.cfg.askDispatchSoundEnabled === false) return;
42
+ const target = state.terms.get(msg.toId);
43
+ if (target?.muted) return;
44
+ const sound = state.cfg.askDispatchSound || 'agent-dispatch-ambient';
45
+ new Audio(`/fx/${sound}.mp3`).play().catch(() => {});
46
+ }
47
+
40
48
  function connect() {
41
49
  const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
42
50
  state.ws = new WebSocket(`${wsProtocol}//${location.host}`);
@@ -122,6 +130,9 @@ function connect() {
122
130
  case 'session.status':
123
131
  setStatus(msg.id, msg.working);
124
132
  break;
133
+ case 'session.dispatch':
134
+ playAskDispatchSound(msg);
135
+ break;
125
136
  // Server requests terminal capture (e.g. after PermissionRequest hook)
126
137
  case 'terminal.capture': {
127
138
  const ce = state.terms.get(msg.id);
@@ -565,6 +565,11 @@ function renderNotifications() {
565
565
  document.getElementById('cfg-notify-sound').checked = soundEnabled;
566
566
  document.getElementById('notify-sound-row').classList.toggle('hidden', !soundEnabled);
567
567
  document.getElementById('cfg-notify-sound-pick').value = state.cfg.notifySound || 'default-beep';
568
+
569
+ const dispatchSoundEnabled = state.cfg.askDispatchSoundEnabled !== false;
570
+ document.getElementById('cfg-ask-dispatch-sound').checked = dispatchSoundEnabled;
571
+ document.getElementById('ask-dispatch-sound-row').classList.toggle('hidden', !dispatchSoundEnabled);
572
+ document.getElementById('cfg-ask-dispatch-sound-pick').value = state.cfg.askDispatchSound || 'agent-dispatch-ambient';
568
573
  }
569
574
 
570
575
  document.getElementById('cfg-notify-idle').addEventListener('change', (e) => {
@@ -589,6 +594,18 @@ document.getElementById('btn-sound-preview').addEventListener('click', () => {
589
594
  new Audio(`/fx/${sound}.mp3`).play().catch(() => {});
590
595
  });
591
596
 
597
+ document.getElementById('cfg-ask-dispatch-sound').addEventListener('change', (e) => {
598
+ document.getElementById('ask-dispatch-sound-row').classList.toggle('hidden', !e.target.checked);
599
+ saveConfig();
600
+ });
601
+
602
+ document.getElementById('cfg-ask-dispatch-sound-pick').addEventListener('change', saveConfig);
603
+
604
+ document.getElementById('btn-ask-dispatch-sound-preview').addEventListener('click', () => {
605
+ const sound = document.getElementById('cfg-ask-dispatch-sound-pick').value;
606
+ new Audio(`/fx/${sound}.mp3`).play().catch(() => {});
607
+ });
608
+
592
609
  // ── Save ──
593
610
 
594
611
  function saveConfig() {
@@ -628,6 +645,8 @@ function saveConfig() {
628
645
  state.cfg.notifyMinWork = parseInt(document.getElementById('cfg-notify-min-work').value, 10) || 0;
629
646
  state.cfg.notifySoundEnabled = document.getElementById('cfg-notify-sound').checked;
630
647
  state.cfg.notifySound = document.getElementById('cfg-notify-sound-pick').value;
648
+ state.cfg.askDispatchSoundEnabled = document.getElementById('cfg-ask-dispatch-sound').checked;
649
+ state.cfg.askDispatchSound = document.getElementById('cfg-ask-dispatch-sound-pick').value;
631
650
  // Preserve fields not managed by this form
632
651
  // (projects, prompts, etc. live on state.cfg and must not be dropped)
633
652
  send({ type: 'config.update', config: state.cfg });
package/session-ask.js CHANGED
@@ -214,6 +214,13 @@ async function askSession(payload, sessionsApi, cfg = {}) {
214
214
 
215
215
  console.log(`[ask] ${caller.name || callerId.slice(0, 8)} -> ${target.name || targetId.slice(0, 8)} (${timeoutMs}ms timeout)`);
216
216
  const cancelSubmit = submitAskInput(sessionsApi, targetId, injected);
217
+ sessionsApi.broadcast({
218
+ type: 'session.dispatch',
219
+ fromId: callerId,
220
+ fromName: caller.name || callerId.slice(0, 8),
221
+ toId: targetId,
222
+ toName: target.name || targetId.slice(0, 8),
223
+ });
217
224
 
218
225
  const response = await waitForAnswer({ sessionsApi, targetId, sinceTs, timeoutMs }).finally(cancelSubmit);
219
226
  console.log(`[ask] completed ${target.name || targetId.slice(0, 8)} -> ${caller.name || callerId.slice(0, 8)}`);
package/sessions.js CHANGED
@@ -11,6 +11,7 @@ const piBridge = require('./pi-bridge');
11
11
  const plugins = require('./plugin-loader');
12
12
  const { presetForCommand } = require('./preset-utils');
13
13
  const { stripAnsi } = require('./ansi-utils');
14
+ const { withCliDeckGuide } = require('./agent-session-guide');
14
15
 
15
16
  const THEMES = require('./themes');
16
17
  const MAX_BUFFER = 2 * 1024 * 1024;
@@ -98,12 +99,14 @@ function isLightTheme(themeId) {
98
99
  }
99
100
 
100
101
  function spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken, projectId, cols, rows) {
102
+ const preset = matchPreset(cmd);
103
+ const launchParts = withCliDeckGuide(parts, preset?.presetId);
101
104
  const telemetryEnv = buildTelemetryEnv(id, cmd);
102
105
  const colorEnv = isLightTheme(themeId) ? { COLORFGBG: '0;15' } : { COLORFGBG: '15;0' };
103
106
  const extraEnv = commandEnv(cmd);
104
107
  let term;
105
108
  try {
106
- term = pty.spawn(parts[0], parts.slice(1), {
109
+ term = pty.spawn(launchParts[0], launchParts.slice(1), {
107
110
  name: 'xterm-256color', cols: cols || 80, rows: rows || 24, cwd,
108
111
  env: { ...process.env, ...extraEnv, ...telemetryEnv, ...colorEnv },
109
112
  });
@@ -112,7 +115,6 @@ function spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken,
112
115
  }
113
116
 
114
117
  const sessionIdRe = cmd.sessionIdPattern ? new RegExp(cmd.sessionIdPattern, 'i') : null;
115
- const preset = matchPreset(cmd);
116
118
  const session = { name, themeId, commandId, cwd, pty: term, chunks: [], chunksSize: 0, sessionToken: savedToken || null, projectId: projectId || null, presetId: preset?.presetId || 'shell', working: undefined };
117
119
  sessions.set(id, session);
118
120
  transcript.setFinalizeOnIdle(id, ['claude-code', 'codex', 'gemini-cli', 'opencode', 'pi', 'clideck-agent'].includes(session.presetId) ? session.presetId : null);