clideck 1.31.22 → 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.
- package/agent-session-guide.js +43 -0
- package/bin/clideck.js +17 -4
- package/clideck-agents-cli.js +2 -0
- package/clideck-ask-cli.js +61 -4
- package/config.js +2 -0
- package/handlers.js +7 -7
- package/package.json +1 -1
- package/public/fx/agent-dispatch-ambient.mp3 +0 -0
- package/public/fx/agent-dispatch-soft.mp3 +0 -0
- package/public/index.html +21 -1
- package/public/js/app.js +11 -0
- package/public/js/settings.js +19 -0
- package/session-ask.js +7 -0
- package/sessions.js +4 -2
|
@@ -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,31 +26,43 @@ 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.',
|
|
31
34
|
' Cross-project asks must use an explicit @project/session target.',
|
|
35
|
+
' Use the target exactly as shown by `clideck agents`; quote it if it contains spaces.',
|
|
32
36
|
' CliDeck sends the message into the real target terminal, presses Enter, waits for the',
|
|
33
37
|
' target to finish, then prints the target agent response to stdout.',
|
|
34
38
|
' The target is another LLM agent. It may need minutes to think, read files, and use tools.',
|
|
35
|
-
'
|
|
36
|
-
'
|
|
39
|
+
' Keep the `clideck ask` shell command running until it exits. stdout is the response channel.',
|
|
40
|
+
' Waiting progress goes to stderr; the target response goes to stdout.',
|
|
41
|
+
' Set both `--timeout` and your shell/tool-call timeout high enough, or the target may keep',
|
|
42
|
+
' working while the caller loses the response.',
|
|
43
|
+
' CliDeck only sends to idle targets and does not queue asks. If the target is busy, retry',
|
|
44
|
+
' later or ask another idle agent.',
|
|
45
|
+
' To ask multiple agents, run one `clideck ask` command per target and keep each one open.',
|
|
37
46
|
'',
|
|
38
47
|
'Examples:',
|
|
39
48
|
' clideck agents',
|
|
40
49
|
' clideck agents --json',
|
|
41
50
|
' clideck agents --all',
|
|
51
|
+
' clideck ask status',
|
|
52
|
+
' clideck ask status --all',
|
|
42
53
|
' clideck ask --session "Reviewer" --message "Review my changes and return only findings."',
|
|
43
54
|
' clideck ask "research manager" "Check this plan and tell me what is missing." --timeout 15m',
|
|
44
55
|
' clideck ask "@website/Docs Writer" "Check if the docs mention the new CLI flags." --timeout 15m',
|
|
45
56
|
' cat notes.md | clideck ask --session "Docs Writer" --timeout 10m',
|
|
46
57
|
'',
|
|
47
58
|
'Notes for agents:',
|
|
59
|
+
' CliDeck supports agent-to-agent communication: discover teammates with `clideck agents`,',
|
|
60
|
+
' then ask them directly with `clideck ask`.',
|
|
48
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.',
|
|
49
63
|
' Run `clideck agents --all` before a cross-project ask.',
|
|
50
64
|
' Run `clideck ask --help` for the exact ask command contract.',
|
|
51
|
-
' If
|
|
52
|
-
' If several sessions have the same name in the same project, use the session id.',
|
|
65
|
+
' If a target has spaces, quote the whole target, including @project/session addresses.',
|
|
53
66
|
].join('\n');
|
|
54
67
|
}
|
|
55
68
|
|
package/clideck-agents-cli.js
CHANGED
|
@@ -7,8 +7,10 @@ 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.',
|
|
13
|
+
'Use the printed ask= value exactly when present, and prefer idle targets for `clideck ask`.',
|
|
12
14
|
'',
|
|
13
15
|
'Options:',
|
|
14
16
|
' --json Print machine-readable JSON.',
|
package/clideck-ask-cli.js
CHANGED
|
@@ -4,32 +4,59 @@ 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.',
|
|
19
|
+
'Copy and use the target exactly as listed by `clideck agents`; quote it if it contains spaces.',
|
|
16
20
|
'',
|
|
17
21
|
'Important for agents:',
|
|
18
22
|
' The target is another LLM agent, not a fast CLI command. It may need minutes to read files,',
|
|
19
|
-
' think, use tools, and answer.
|
|
20
|
-
' shell
|
|
21
|
-
'
|
|
23
|
+
' think, use tools, and answer.',
|
|
24
|
+
' Keep this shell command running until it exits. The target agent response is returned through',
|
|
25
|
+
' this command\'s stdout; if you background it, stop watching it, or let your shell/tool timeout',
|
|
26
|
+
' kill it, the target may keep working but you will lose the response channel.',
|
|
27
|
+
' Set BOTH `clideck ask --timeout` and your own shell/tool-call timeout high enough.',
|
|
28
|
+
' Waiting progress is printed to stderr; the target response is printed to stdout.',
|
|
29
|
+
' CliDeck only sends to idle target agents. If the target is busy, the ask fails immediately;',
|
|
30
|
+
' it is not queued. Try again later or ask another idle agent.',
|
|
31
|
+
' To ask multiple agents, start one `clideck ask` command per target and keep each command open.',
|
|
22
32
|
'',
|
|
23
33
|
'Options:',
|
|
34
|
+
' status Show idle/busy state for active project sessions.',
|
|
24
35
|
' -s, --session <name-or-id> Target session name or id.',
|
|
25
36
|
' -m, --message <text> Message to send. If omitted, stdin is used.',
|
|
26
37
|
' -t, --timeout <duration> Wait time. Examples: 30s, 10m, 1h. Default: 10m.',
|
|
27
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.',
|
|
28
41
|
' --no-progress Do not print waiting hints to stderr.',
|
|
29
42
|
' -h, --help Show this help.',
|
|
30
43
|
].join('\n');
|
|
31
44
|
}
|
|
32
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
|
+
|
|
33
60
|
function parseDuration(value) {
|
|
34
61
|
if (!value) return 10 * 60 * 1000;
|
|
35
62
|
const m = String(value).trim().match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/i);
|
|
@@ -126,6 +153,32 @@ function findAgent(agents, target) {
|
|
|
126
153
|
return insensitive.length === 1 ? insensitive[0] : null;
|
|
127
154
|
}
|
|
128
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
|
+
|
|
129
182
|
function startProgressHints(opts, callerSessionId) {
|
|
130
183
|
if (!opts.progress) return () => {};
|
|
131
184
|
const started = Date.now();
|
|
@@ -205,6 +258,10 @@ function postJson(url, payload, timeoutMs) {
|
|
|
205
258
|
|
|
206
259
|
async function run(args) {
|
|
207
260
|
try {
|
|
261
|
+
if (args[0] === 'status') {
|
|
262
|
+
await runStatus(args.slice(1));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
208
265
|
const opts = parseArgs(args);
|
|
209
266
|
if (opts.help) {
|
|
210
267
|
console.log(usage());
|
|
@@ -229,4 +286,4 @@ async function run(args) {
|
|
|
229
286
|
}
|
|
230
287
|
}
|
|
231
288
|
|
|
232
|
-
module.exports = { run, parseArgs, parseDuration };
|
|
289
|
+
module.exports = { run, parseArgs, parseDuration, parseStatusArgs, formatStatus };
|
package/config.js
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
Binary file
|
|
Binary file
|
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
|
|
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);
|
package/public/js/settings.js
CHANGED
|
@@ -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(
|
|
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);
|