skimpyclaw 0.3.14 → 0.4.0
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/README.md +47 -37
- package/dist/__tests__/adapter-types.test.d.ts +4 -0
- package/dist/__tests__/adapter-types.test.js +63 -0
- package/dist/__tests__/anthropic-adapter.test.d.ts +4 -0
- package/dist/__tests__/anthropic-adapter.test.js +264 -0
- package/dist/__tests__/api.test.js +0 -1
- package/dist/__tests__/cli.integration.test.js +2 -4
- package/dist/__tests__/cli.test.js +0 -1
- package/dist/__tests__/code-agents-notifications.test.js +137 -0
- package/dist/__tests__/code-agents-parser.test.js +19 -1
- package/dist/__tests__/code-agents-preflight.test.js +3 -28
- package/dist/__tests__/code-agents-utils.test.js +34 -9
- package/dist/__tests__/code-agents-worktrees.test.js +116 -0
- package/dist/__tests__/codex-adapter.test.js +184 -0
- package/dist/__tests__/codex-auth.test.js +66 -0
- package/dist/__tests__/codex-provider-gating.test.js +35 -0
- package/dist/__tests__/codex-unified-loop.test.js +111 -0
- package/dist/__tests__/config-security.test.js +127 -0
- package/dist/__tests__/config.test.js +23 -0
- package/dist/__tests__/context-manager.test.js +243 -164
- package/dist/__tests__/cron-run.test.js +250 -0
- package/dist/__tests__/cron.test.js +12 -38
- package/dist/__tests__/digests.test.js +67 -0
- package/dist/__tests__/discord-attachments.test.js +211 -0
- package/dist/__tests__/discord-docs.test.d.ts +1 -0
- package/dist/__tests__/discord-docs.test.js +27 -0
- package/dist/__tests__/discord-thread-agents.test.d.ts +1 -0
- package/dist/__tests__/discord-thread-agents.test.js +115 -0
- package/dist/__tests__/discord-thread-context.test.d.ts +1 -0
- package/dist/__tests__/discord-thread-context.test.js +42 -0
- package/dist/__tests__/doctor.formatters.test.js +4 -4
- package/dist/__tests__/doctor.index.test.js +1 -1
- package/dist/__tests__/doctor.runner.test.js +3 -15
- package/dist/__tests__/env-sanitizer.test.d.ts +1 -0
- package/dist/__tests__/env-sanitizer.test.js +45 -0
- package/dist/__tests__/exec-approval.test.js +61 -0
- package/dist/__tests__/fetch-tool.test.d.ts +1 -0
- package/dist/__tests__/fetch-tool.test.js +85 -0
- package/dist/__tests__/gateway-status-auth.test.d.ts +1 -0
- package/dist/__tests__/gateway-status-auth.test.js +72 -0
- package/dist/__tests__/heartbeat.test.js +3 -3
- package/dist/__tests__/interactive-sessions.test.d.ts +1 -0
- package/dist/__tests__/interactive-sessions.test.js +96 -0
- package/dist/__tests__/langfuse.test.js +6 -18
- package/dist/__tests__/model-selection.test.js +3 -4
- package/dist/__tests__/providers-init.test.js +2 -8
- package/dist/__tests__/providers-routing.test.js +1 -1
- package/dist/__tests__/providers-utils.test.js +13 -3
- package/dist/__tests__/sessions.test.js +14 -10
- package/dist/__tests__/setup.test.js +12 -29
- package/dist/__tests__/skills.test.js +10 -7
- package/dist/__tests__/stream-formatter.test.d.ts +1 -0
- package/dist/__tests__/stream-formatter.test.js +114 -0
- package/dist/__tests__/token-efficiency.test.js +131 -15
- package/dist/__tests__/tool-loop.test.d.ts +4 -0
- package/dist/__tests__/tool-loop.test.js +505 -0
- package/dist/__tests__/tools.test.js +101 -276
- package/dist/__tests__/utils.test.d.ts +1 -0
- package/dist/__tests__/utils.test.js +14 -0
- package/dist/__tests__/voice.test.js +21 -0
- package/dist/agent.js +35 -4
- package/dist/api.js +113 -37
- package/dist/channels/discord/attachments.d.ts +50 -0
- package/dist/channels/discord/attachments.js +137 -0
- package/dist/channels/discord/delegation.d.ts +5 -0
- package/dist/channels/discord/delegation.js +136 -0
- package/dist/channels/discord/handlers.js +694 -7
- package/dist/channels/discord/index.d.ts +16 -1
- package/dist/channels/discord/index.js +64 -1
- package/dist/channels/discord/thread-agents.d.ts +54 -0
- package/dist/channels/discord/thread-agents.js +323 -0
- package/dist/channels/discord/threads.d.ts +58 -0
- package/dist/channels/discord/threads.js +192 -0
- package/dist/channels/discord/types.js +4 -2
- package/dist/channels/discord/utils.d.ts +16 -0
- package/dist/channels/discord/utils.js +86 -6
- package/dist/channels/telegram/index.d.ts +1 -1
- package/dist/channels/telegram/types.js +1 -1
- package/dist/channels/telegram/utils.js +9 -3
- package/dist/channels.d.ts +1 -1
- package/dist/cli.js +20 -400
- package/dist/code-agents/executor.d.ts +1 -1
- package/dist/code-agents/executor.js +101 -45
- package/dist/code-agents/index.d.ts +2 -7
- package/dist/code-agents/index.js +111 -80
- package/dist/code-agents/interactive-resume.d.ts +6 -0
- package/dist/code-agents/interactive-resume.js +98 -0
- package/dist/code-agents/interactive-sessions.d.ts +20 -0
- package/dist/code-agents/interactive-sessions.js +132 -0
- package/dist/code-agents/parser.js +5 -1
- package/dist/code-agents/registry.d.ts +7 -1
- package/dist/code-agents/registry.js +11 -23
- package/dist/code-agents/stream-formatter.d.ts +8 -0
- package/dist/code-agents/stream-formatter.js +92 -0
- package/dist/code-agents/types.d.ts +16 -24
- package/dist/code-agents/utils.d.ts +35 -11
- package/dist/code-agents/utils.js +349 -95
- package/dist/code-agents/worktrees.d.ts +37 -0
- package/dist/code-agents/worktrees.js +116 -0
- package/dist/config.d.ts +2 -4
- package/dist/config.js +123 -23
- package/dist/cron.d.ts +1 -6
- package/dist/cron.js +175 -82
- package/dist/dashboard/assets/index-B345aOO-.js +65 -0
- package/dist/dashboard/assets/index-ZWK4dalJ.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/digests.d.ts +1 -0
- package/dist/digests.js +132 -42
- package/dist/doctor/checks.d.ts +0 -3
- package/dist/doctor/checks.js +1 -108
- package/dist/doctor/runner.js +1 -4
- package/dist/env-sanitizer.d.ts +2 -0
- package/dist/env-sanitizer.js +61 -0
- package/dist/exec-approval.d.ts +11 -1
- package/dist/exec-approval.js +17 -4
- package/dist/gateway.d.ts +3 -1
- package/dist/gateway.js +17 -7
- package/dist/heartbeat.js +1 -6
- package/dist/langfuse.js +3 -29
- package/dist/model-selection.js +3 -1
- package/dist/providers/adapter.d.ts +118 -0
- package/dist/providers/adapter.js +6 -0
- package/dist/providers/adapters/anthropic-adapter.d.ts +22 -0
- package/dist/providers/adapters/anthropic-adapter.js +204 -0
- package/dist/providers/adapters/codex-adapter.d.ts +26 -0
- package/dist/providers/adapters/codex-adapter.js +203 -0
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.js +10 -272
- package/dist/providers/codex.d.ts +21 -0
- package/dist/providers/codex.js +149 -330
- package/dist/providers/content.d.ts +1 -1
- package/dist/providers/content.js +2 -2
- package/dist/providers/context-manager.d.ts +18 -6
- package/dist/providers/context-manager.js +199 -223
- package/dist/providers/index.d.ts +9 -1
- package/dist/providers/index.js +73 -64
- package/dist/providers/loop-utils.d.ts +20 -0
- package/dist/providers/loop-utils.js +30 -0
- package/dist/providers/tool-loop.d.ts +12 -0
- package/dist/providers/tool-loop.js +251 -0
- package/dist/providers/utils.d.ts +19 -3
- package/dist/providers/utils.js +100 -29
- package/dist/secure-store.d.ts +8 -0
- package/dist/secure-store.js +80 -0
- package/dist/service.js +3 -28
- package/dist/sessions.d.ts +3 -0
- package/dist/sessions.js +147 -18
- package/dist/setup-templates.js +13 -25
- package/dist/setup.d.ts +10 -6
- package/dist/setup.js +84 -292
- package/dist/skills.js +3 -11
- package/dist/tools/agent-delegation.d.ts +19 -0
- package/dist/tools/agent-delegation.js +49 -0
- package/dist/tools/bash-tool.js +89 -34
- package/dist/tools/definitions.d.ts +199 -302
- package/dist/tools/definitions.js +70 -123
- package/dist/tools/execute-context.d.ts +13 -4
- package/dist/tools/fetch-tool.js +109 -13
- package/dist/tools/file-tools.js +7 -1
- package/dist/tools.d.ts +7 -7
- package/dist/tools.js +133 -151
- package/dist/types.d.ts +37 -30
- package/dist/utils.js +4 -6
- package/dist/voice.d.ts +1 -1
- package/dist/voice.js +17 -4
- package/package.json +33 -23
- package/templates/TOOLS.md +0 -27
- package/dist/__tests__/audit.test.js +0 -122
- package/dist/__tests__/code-agents-orchestrator.test.js +0 -216
- package/dist/__tests__/code-agents-sandbox.test.js +0 -163
- package/dist/__tests__/orchestrator.test.js +0 -425
- package/dist/__tests__/sandbox-bridge.test.js +0 -116
- package/dist/__tests__/sandbox-manager.test.js +0 -144
- package/dist/__tests__/sandbox-mount-security.test.js +0 -139
- package/dist/__tests__/sandbox-runtime.test.js +0 -176
- package/dist/__tests__/subagent.test.js +0 -240
- package/dist/__tests__/telegram.test.js +0 -42
- package/dist/code-agents/orchestrator.d.ts +0 -29
- package/dist/code-agents/orchestrator.js +0 -694
- package/dist/code-agents/worktree.d.ts +0 -40
- package/dist/code-agents/worktree.js +0 -215
- package/dist/dashboard/assets/index-BoTHPby4.js +0 -65
- package/dist/dashboard/assets/index-D4mufvBg.css +0 -1
- package/dist/dashboard.d.ts +0 -8
- package/dist/dashboard.js +0 -4071
- package/dist/discord.d.ts +0 -8
- package/dist/discord.js +0 -792
- package/dist/mcp-context-a8c.d.ts +0 -13
- package/dist/mcp-context-a8c.js +0 -34
- package/dist/orchestrator.d.ts +0 -15
- package/dist/orchestrator.js +0 -676
- package/dist/providers/openai.d.ts +0 -10
- package/dist/providers/openai.js +0 -355
- package/dist/sandbox/bridge.d.ts +0 -5
- package/dist/sandbox/bridge.js +0 -63
- package/dist/sandbox/index.d.ts +0 -5
- package/dist/sandbox/index.js +0 -4
- package/dist/sandbox/manager.d.ts +0 -7
- package/dist/sandbox/manager.js +0 -100
- package/dist/sandbox/mount-security.d.ts +0 -12
- package/dist/sandbox/mount-security.js +0 -122
- package/dist/sandbox/runtime.d.ts +0 -39
- package/dist/sandbox/runtime.js +0 -192
- package/dist/sandbox-utils.d.ts +0 -6
- package/dist/sandbox-utils.js +0 -36
- package/dist/subagent.d.ts +0 -19
- package/dist/subagent.js +0 -407
- package/dist/telegram.d.ts +0 -2
- package/dist/telegram.js +0 -11
- package/dist/tools/browser-tool.d.ts +0 -3
- package/dist/tools/browser-tool.js +0 -266
- package/sandbox/Dockerfile +0 -40
- /package/dist/__tests__/{audit.test.d.ts → code-agents-notifications.test.d.ts} +0 -0
- /package/dist/__tests__/{code-agents-orchestrator.test.d.ts → code-agents-worktrees.test.d.ts} +0 -0
- /package/dist/__tests__/{code-agents-sandbox.test.d.ts → codex-adapter.test.d.ts} +0 -0
- /package/dist/__tests__/{orchestrator.test.d.ts → codex-auth.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-bridge.test.d.ts → codex-provider-gating.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-manager.test.d.ts → codex-unified-loop.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-mount-security.test.d.ts → config-security.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-runtime.test.d.ts → cron-run.test.d.ts} +0 -0
- /package/dist/__tests__/{subagent.test.d.ts → digests.test.d.ts} +0 -0
- /package/dist/__tests__/{telegram.test.d.ts → discord-attachments.test.d.ts} +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Runs a single --resume turn against a live interactive coding session.
|
|
2
|
+
// Called from the Discord thread-message handler when it detects a user message
|
|
3
|
+
// in a thread that is mapped to an active interactive session.
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import { CLAUDE_CLI_PATH, buildCodeAgentSpawnEnv } from './utils.js';
|
|
6
|
+
import { enqueue, dequeue, markIdle, getSession, touchActivity, updateStatus } from './interactive-sessions.js';
|
|
7
|
+
import { chunkForDiscord } from './stream-formatter.js';
|
|
8
|
+
export async function handleInteractiveThreadMessage(opts) {
|
|
9
|
+
const { discordThreadId, userMessage, postToThread } = opts;
|
|
10
|
+
const session = getSession(discordThreadId);
|
|
11
|
+
if (!session)
|
|
12
|
+
return; // not ours — normal handler will take it
|
|
13
|
+
if (session.status !== 'active') {
|
|
14
|
+
await postToThread([`⚠ this session is ${session.status}; start a new interactive \`code_with_agent\` call to begin again`]);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const { shouldStart } = enqueue(discordThreadId, userMessage);
|
|
18
|
+
if (!shouldStart)
|
|
19
|
+
return;
|
|
20
|
+
try {
|
|
21
|
+
for (;;) {
|
|
22
|
+
const pending = dequeue(discordThreadId);
|
|
23
|
+
if (!pending)
|
|
24
|
+
break;
|
|
25
|
+
try {
|
|
26
|
+
const stdout = await runClaudeResumeSubprocess(session.cliSessionId, pending.content);
|
|
27
|
+
touchActivity(discordThreadId);
|
|
28
|
+
const chunks = chunkForDiscord(stdout);
|
|
29
|
+
if (chunks.length > 0)
|
|
30
|
+
await postToThread(chunks);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
34
|
+
updateStatus(discordThreadId, 'errored');
|
|
35
|
+
await postToThread([`✗ subprocess crashed: ${msg.slice(0, 500)}`]);
|
|
36
|
+
// Drain the rest of the queue so stranded messages don't sit forever.
|
|
37
|
+
// The session is errored — emit a single notice instead of retrying.
|
|
38
|
+
let dropped = 0;
|
|
39
|
+
while (dequeue(discordThreadId))
|
|
40
|
+
dropped++;
|
|
41
|
+
if (dropped > 0) {
|
|
42
|
+
await postToThread([`(dropped ${dropped} queued message${dropped === 1 ? '' : 's'} because the session errored)`]);
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
markIdle(discordThreadId);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function runClaudeResumeSubprocess(cliSessionId, message) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const env = buildCodeAgentSpawnEnv();
|
|
55
|
+
const args = [
|
|
56
|
+
'-p',
|
|
57
|
+
'--resume', cliSessionId,
|
|
58
|
+
'--dangerously-skip-permissions',
|
|
59
|
+
message,
|
|
60
|
+
];
|
|
61
|
+
const msgPreview = message.length > 80 ? message.slice(0, 80) + '...' : message;
|
|
62
|
+
console.log(`[interactive-resume] spawn: claude -p --resume ${cliSessionId} "${msgPreview}"`);
|
|
63
|
+
const proc = spawn(CLAUDE_CLI_PATH, args, {
|
|
64
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
65
|
+
env,
|
|
66
|
+
});
|
|
67
|
+
let stdout = '';
|
|
68
|
+
let stderr = '';
|
|
69
|
+
proc.stdout.on('data', chunk => { stdout += chunk.toString(); });
|
|
70
|
+
proc.stderr.on('data', chunk => { stderr += chunk.toString(); });
|
|
71
|
+
// Safety: 10min cap per turn.
|
|
72
|
+
const timer = setTimeout(() => {
|
|
73
|
+
try {
|
|
74
|
+
proc.kill('SIGTERM');
|
|
75
|
+
}
|
|
76
|
+
catch { /* best effort */ }
|
|
77
|
+
reject(new Error('resume turn timed out after 10m'));
|
|
78
|
+
}, 10 * 60 * 1000);
|
|
79
|
+
proc.on('close', (code) => {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
if (code === 0) {
|
|
82
|
+
resolve(stdout);
|
|
83
|
+
}
|
|
84
|
+
else if (stdout) {
|
|
85
|
+
// Non-zero exit but we still have output — surface it but log the failure.
|
|
86
|
+
console.warn(`[interactive-resume] claude exited ${code}; returning partial stdout (${stdout.length} chars). stderr: ${stderr.slice(0, 200)}`);
|
|
87
|
+
resolve(stdout);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
reject(new Error(`claude exited with code ${code}: ${stderr.slice(0, 500)}`));
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
proc.on('error', (err) => {
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
reject(err);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { InteractiveSession, InteractiveSessionStatus } from '../types.js';
|
|
2
|
+
interface PendingMessage {
|
|
3
|
+
content: string;
|
|
4
|
+
receivedAt: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function addSession(s: InteractiveSession): void;
|
|
7
|
+
export declare function getSession(discordThreadId: string): InteractiveSession | undefined;
|
|
8
|
+
export declare function updateStatus(discordThreadId: string, status: InteractiveSessionStatus): void;
|
|
9
|
+
export declare function touchActivity(discordThreadId: string): void;
|
|
10
|
+
export declare function listSessions(): InteractiveSession[];
|
|
11
|
+
export declare function enqueue(discordThreadId: string, content: string): {
|
|
12
|
+
shouldStart: boolean;
|
|
13
|
+
};
|
|
14
|
+
export declare function dequeue(discordThreadId: string): PendingMessage | undefined;
|
|
15
|
+
export declare function markIdle(discordThreadId: string): void;
|
|
16
|
+
export declare function hasPending(discordThreadId: string): boolean;
|
|
17
|
+
export declare function addPendingSession(codeAgentTaskId: string, s: Omit<InteractiveSession, 'discordThreadId'>): void;
|
|
18
|
+
export declare function linkThread(codeAgentTaskId: string, discordThreadId: string): boolean;
|
|
19
|
+
export declare function _resetForTesting(): void;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Interactive coding session state store.
|
|
2
|
+
//
|
|
3
|
+
// Maps Discord thread IDs to running CLI sessions (claude --session-id or
|
|
4
|
+
// codex thread_id). Persisted to disk so sessions survive gateway restarts.
|
|
5
|
+
// Per-session FIFO queue prevents concurrent --resume subprocesses against
|
|
6
|
+
// the same session (which would corrupt history).
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
8
|
+
import { dirname } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
const STORE_PATH = join(homedir(), '.skimpyclaw', 'logs', 'code-agents', 'interactive-sessions.json');
|
|
12
|
+
const sessions = new Map();
|
|
13
|
+
const queues = new Map();
|
|
14
|
+
// Pending sessions created before a Discord thread exists, keyed by coding-agent task ID.
|
|
15
|
+
const pendingByTaskId = new Map();
|
|
16
|
+
let loaded = false;
|
|
17
|
+
function ensureLoaded() {
|
|
18
|
+
if (loaded)
|
|
19
|
+
return;
|
|
20
|
+
loaded = true;
|
|
21
|
+
if (!existsSync(STORE_PATH))
|
|
22
|
+
return;
|
|
23
|
+
try {
|
|
24
|
+
const raw = readFileSync(STORE_PATH, 'utf-8');
|
|
25
|
+
const parsed = JSON.parse(raw);
|
|
26
|
+
for (const s of parsed) {
|
|
27
|
+
sessions.set(s.discordThreadId, s);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
console.error('[interactive-sessions] Failed to load store:', err);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function persist() {
|
|
35
|
+
try {
|
|
36
|
+
mkdirSync(dirname(STORE_PATH), { recursive: true });
|
|
37
|
+
const arr = Array.from(sessions.values());
|
|
38
|
+
writeFileSync(STORE_PATH, JSON.stringify(arr, null, 2), 'utf-8');
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error('[interactive-sessions] Failed to persist store:', err);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function addSession(s) {
|
|
45
|
+
ensureLoaded();
|
|
46
|
+
sessions.set(s.discordThreadId, s);
|
|
47
|
+
queues.set(s.discordThreadId, { inFlight: false, queue: [] });
|
|
48
|
+
persist();
|
|
49
|
+
}
|
|
50
|
+
export function getSession(discordThreadId) {
|
|
51
|
+
ensureLoaded();
|
|
52
|
+
return sessions.get(discordThreadId);
|
|
53
|
+
}
|
|
54
|
+
export function updateStatus(discordThreadId, status) {
|
|
55
|
+
ensureLoaded();
|
|
56
|
+
const s = sessions.get(discordThreadId);
|
|
57
|
+
if (!s)
|
|
58
|
+
return;
|
|
59
|
+
s.status = status;
|
|
60
|
+
s.lastActivityAt = new Date().toISOString();
|
|
61
|
+
persist();
|
|
62
|
+
}
|
|
63
|
+
export function touchActivity(discordThreadId) {
|
|
64
|
+
ensureLoaded();
|
|
65
|
+
const s = sessions.get(discordThreadId);
|
|
66
|
+
if (!s)
|
|
67
|
+
return;
|
|
68
|
+
s.lastActivityAt = new Date().toISOString();
|
|
69
|
+
persist();
|
|
70
|
+
}
|
|
71
|
+
export function listSessions() {
|
|
72
|
+
ensureLoaded();
|
|
73
|
+
return Array.from(sessions.values());
|
|
74
|
+
}
|
|
75
|
+
// `enqueue` returns shouldStart=true if the caller should begin draining;
|
|
76
|
+
// false if a subprocess is already in flight and will pick up this message.
|
|
77
|
+
export function enqueue(discordThreadId, content) {
|
|
78
|
+
ensureLoaded();
|
|
79
|
+
let q = queues.get(discordThreadId);
|
|
80
|
+
if (!q) {
|
|
81
|
+
q = { inFlight: false, queue: [] };
|
|
82
|
+
queues.set(discordThreadId, q);
|
|
83
|
+
}
|
|
84
|
+
q.queue.push({ content, receivedAt: new Date().toISOString() });
|
|
85
|
+
if (!q.inFlight) {
|
|
86
|
+
q.inFlight = true;
|
|
87
|
+
return { shouldStart: true };
|
|
88
|
+
}
|
|
89
|
+
return { shouldStart: false };
|
|
90
|
+
}
|
|
91
|
+
export function dequeue(discordThreadId) {
|
|
92
|
+
const q = queues.get(discordThreadId);
|
|
93
|
+
if (!q)
|
|
94
|
+
return undefined;
|
|
95
|
+
return q.queue.shift();
|
|
96
|
+
}
|
|
97
|
+
export function markIdle(discordThreadId) {
|
|
98
|
+
const q = queues.get(discordThreadId);
|
|
99
|
+
if (!q)
|
|
100
|
+
return;
|
|
101
|
+
q.inFlight = false;
|
|
102
|
+
}
|
|
103
|
+
export function hasPending(discordThreadId) {
|
|
104
|
+
const q = queues.get(discordThreadId);
|
|
105
|
+
if (!q)
|
|
106
|
+
return false;
|
|
107
|
+
return q.queue.length > 0;
|
|
108
|
+
}
|
|
109
|
+
// Register a session that doesn't yet have a Discord thread. Keyed by the
|
|
110
|
+
// coding-agent task ID; the Discord handler re-keys by threadId via linkThread().
|
|
111
|
+
export function addPendingSession(codeAgentTaskId, s) {
|
|
112
|
+
ensureLoaded();
|
|
113
|
+
pendingByTaskId.set(codeAgentTaskId, { ...s, discordThreadId: '' });
|
|
114
|
+
}
|
|
115
|
+
export function linkThread(codeAgentTaskId, discordThreadId) {
|
|
116
|
+
ensureLoaded();
|
|
117
|
+
const pending = pendingByTaskId.get(codeAgentTaskId);
|
|
118
|
+
if (!pending)
|
|
119
|
+
return false;
|
|
120
|
+
const full = { ...pending, discordThreadId };
|
|
121
|
+
sessions.set(discordThreadId, full);
|
|
122
|
+
queues.set(discordThreadId, { inFlight: false, queue: [] });
|
|
123
|
+
pendingByTaskId.delete(codeAgentTaskId);
|
|
124
|
+
persist();
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
export function _resetForTesting() {
|
|
128
|
+
sessions.clear();
|
|
129
|
+
queues.clear();
|
|
130
|
+
pendingByTaskId.clear();
|
|
131
|
+
loaded = false;
|
|
132
|
+
}
|
|
@@ -181,9 +181,13 @@ export function parseClaudeOutput(stdout) {
|
|
|
181
181
|
export function parseCodexOutput(stdout) {
|
|
182
182
|
const lines = stdout.trim().split('\n');
|
|
183
183
|
const outputs = [];
|
|
184
|
+
let sawJsonEvent = false;
|
|
184
185
|
for (const line of lines) {
|
|
185
186
|
try {
|
|
186
187
|
const obj = JSON.parse(line);
|
|
188
|
+
if (obj && typeof obj === 'object' && typeof obj.type === 'string') {
|
|
189
|
+
sawJsonEvent = true;
|
|
190
|
+
}
|
|
187
191
|
// Standard output_text events
|
|
188
192
|
if (obj.type === 'output_text' || obj.output_text) {
|
|
189
193
|
outputs.push(obj.output_text || obj.text || '');
|
|
@@ -198,5 +202,5 @@ export function parseCodexOutput(stdout) {
|
|
|
198
202
|
outputs.push(line);
|
|
199
203
|
}
|
|
200
204
|
}
|
|
201
|
-
return outputs.join('\n') || stdout || '(no output)';
|
|
205
|
+
return outputs.join('\n') || (sawJsonEvent ? '(no text output)' : stdout) || '(no output)';
|
|
202
206
|
}
|
|
@@ -15,13 +15,19 @@ export declare function getAllCodeAgents(): CodeAgentTask[];
|
|
|
15
15
|
export declare function getCodeAgent(id: string): CodeAgentTask | null;
|
|
16
16
|
/** Store a task in the registry. */
|
|
17
17
|
export declare function storeCodeAgentTask(task: CodeAgentTask): void;
|
|
18
|
+
/**
|
|
19
|
+
* Find tasks spawned from a specific chat/channel that don't have a discordThreadId yet.
|
|
20
|
+
* Used by Discord handler to retroactively assign threads after runAgentTurn.
|
|
21
|
+
* Includes non-running tasks to handle fast-completing agents (race condition fix).
|
|
22
|
+
*/
|
|
23
|
+
export declare function getUnthreadedTasksForChat(chatId: number): CodeAgentTask[];
|
|
18
24
|
/** Get the canceller function for a task. */
|
|
19
25
|
export declare function getCodeAgentCanceller(id: string): (() => void) | undefined;
|
|
20
26
|
/** Set the canceller function for a task. */
|
|
21
27
|
export declare function setCodeAgentCanceller(id: string, canceller: () => void): void;
|
|
22
28
|
/** Delete the canceller function for a task. */
|
|
23
29
|
export declare function deleteCodeAgentCanceller(id: string): void;
|
|
24
|
-
/** Cancel a running/pending code agent.
|
|
30
|
+
/** Cancel a running/pending code agent. */
|
|
25
31
|
export declare function cancelCodeAgent(id: string): CodeAgentTask | null;
|
|
26
32
|
/** Restore code agent tasks from disk on startup. */
|
|
27
33
|
export declare function restoreCodeAgentTasks(): void;
|
|
@@ -57,6 +57,15 @@ export function getCodeAgent(id) {
|
|
|
57
57
|
export function storeCodeAgentTask(task) {
|
|
58
58
|
codeAgentTasks.set(task.id, task);
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Find tasks spawned from a specific chat/channel that don't have a discordThreadId yet.
|
|
62
|
+
* Used by Discord handler to retroactively assign threads after runAgentTurn.
|
|
63
|
+
* Includes non-running tasks to handle fast-completing agents (race condition fix).
|
|
64
|
+
*/
|
|
65
|
+
export function getUnthreadedTasksForChat(chatId) {
|
|
66
|
+
return Array.from(codeAgentTasks.values()).filter(t => t.chatId === chatId &&
|
|
67
|
+
!t.discordThreadId);
|
|
68
|
+
}
|
|
60
69
|
/** Get the canceller function for a task. */
|
|
61
70
|
export function getCodeAgentCanceller(id) {
|
|
62
71
|
return codeAgentCancellers.get(id);
|
|
@@ -69,7 +78,7 @@ export function setCodeAgentCanceller(id, canceller) {
|
|
|
69
78
|
export function deleteCodeAgentCanceller(id) {
|
|
70
79
|
codeAgentCancellers.delete(id);
|
|
71
80
|
}
|
|
72
|
-
/** Cancel a running/pending code agent.
|
|
81
|
+
/** Cancel a running/pending code agent. */
|
|
73
82
|
export function cancelCodeAgent(id) {
|
|
74
83
|
const task = codeAgentTasks.get(id);
|
|
75
84
|
if (!task)
|
|
@@ -77,27 +86,6 @@ export function cancelCodeAgent(id) {
|
|
|
77
86
|
const isTerminal = ['completed', 'failed', 'timeout', 'cancelled'].includes(task.status);
|
|
78
87
|
if (isTerminal)
|
|
79
88
|
return task;
|
|
80
|
-
for (const childId of task.childTaskIds || []) {
|
|
81
|
-
const child = codeAgentTasks.get(childId);
|
|
82
|
-
if (!child)
|
|
83
|
-
continue;
|
|
84
|
-
const childTerminal = ['completed', 'failed', 'timeout', 'cancelled'].includes(child.status);
|
|
85
|
-
if (childTerminal)
|
|
86
|
-
continue;
|
|
87
|
-
const childCanceller = codeAgentCancellers.get(childId);
|
|
88
|
-
if (childCanceller) {
|
|
89
|
-
try {
|
|
90
|
-
childCanceller();
|
|
91
|
-
}
|
|
92
|
-
catch { /* best effort */ }
|
|
93
|
-
}
|
|
94
|
-
child.status = 'cancelled';
|
|
95
|
-
child.endedAt = new Date().toISOString();
|
|
96
|
-
child.durationSeconds = Math.round((Date.now() - new Date(child.startedAt).getTime()) / 1000);
|
|
97
|
-
child.error = 'Cancelled by user';
|
|
98
|
-
child.liveOutput = undefined;
|
|
99
|
-
writeCodeAgentTask(child);
|
|
100
|
-
}
|
|
101
89
|
const canceller = codeAgentCancellers.get(id);
|
|
102
90
|
if (canceller) {
|
|
103
91
|
try {
|
|
@@ -118,7 +106,7 @@ export function restoreCodeAgentTasks() {
|
|
|
118
106
|
try {
|
|
119
107
|
if (!existsSync(CODE_AGENTS_DIR))
|
|
120
108
|
return;
|
|
121
|
-
const files = readdirSync(CODE_AGENTS_DIR).filter(f => f
|
|
109
|
+
const files = readdirSync(CODE_AGENTS_DIR).filter(f => /^ca-\d+\.json$/.test(f));
|
|
122
110
|
let maxCounter = 0;
|
|
123
111
|
for (const file of files) {
|
|
124
112
|
try {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function stripAnsi(s: string): string;
|
|
2
|
+
export declare function chunkForDiscord(raw: string, max?: number): string[];
|
|
3
|
+
export interface CodexParseResult {
|
|
4
|
+
threadId?: string;
|
|
5
|
+
messages: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function parseCodexJsonl(stdout: string): CodexParseResult;
|
|
8
|
+
export declare function formatCodexOutput(messages: string[]): string[];
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Stream formatter for interactive coding sessions.
|
|
2
|
+
//
|
|
3
|
+
// Converts raw CLI stdout into Discord-postable message chunks.
|
|
4
|
+
// - Claude: plain text; strip ANSI; paragraph-aware chunking at <=1900 chars
|
|
5
|
+
// - Codex: JSONL stream; emit agent_message text as messages; condense tool calls
|
|
6
|
+
const MAX_CHUNK = 1900; // Discord hard limit is 2000; 100 char safety margin
|
|
7
|
+
export function stripAnsi(s) {
|
|
8
|
+
// eslint-disable-next-line no-control-regex
|
|
9
|
+
return s.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
10
|
+
}
|
|
11
|
+
// Paragraph-aware chunking: splits on blank-line runs, packs up to max, and
|
|
12
|
+
// hard-splits any paragraph that alone exceeds the limit.
|
|
13
|
+
export function chunkForDiscord(raw, max = MAX_CHUNK) {
|
|
14
|
+
const text = stripAnsi(raw).replace(/\r\n/g, '\n').trimEnd();
|
|
15
|
+
if (!text)
|
|
16
|
+
return [];
|
|
17
|
+
if (text.length <= max)
|
|
18
|
+
return [text];
|
|
19
|
+
const paragraphs = text.split(/\n{2,}/);
|
|
20
|
+
const chunks = [];
|
|
21
|
+
let current = '';
|
|
22
|
+
for (const p of paragraphs) {
|
|
23
|
+
if (p.length > max) {
|
|
24
|
+
// Paragraph too big by itself — flush current, then hard-split this paragraph.
|
|
25
|
+
if (current) {
|
|
26
|
+
chunks.push(current);
|
|
27
|
+
current = '';
|
|
28
|
+
}
|
|
29
|
+
for (let i = 0; i < p.length; i += max) {
|
|
30
|
+
chunks.push(p.slice(i, i + max));
|
|
31
|
+
}
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const candidate = current ? current + '\n\n' + p : p;
|
|
35
|
+
if (candidate.length > max) {
|
|
36
|
+
chunks.push(current);
|
|
37
|
+
current = p;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
current = candidate;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (current)
|
|
44
|
+
chunks.push(current);
|
|
45
|
+
return chunks;
|
|
46
|
+
}
|
|
47
|
+
export function parseCodexJsonl(stdout) {
|
|
48
|
+
const lines = stdout.split('\n').map(l => l.trim()).filter(Boolean);
|
|
49
|
+
const messages = [];
|
|
50
|
+
let threadId;
|
|
51
|
+
for (const line of lines) {
|
|
52
|
+
let ev;
|
|
53
|
+
try {
|
|
54
|
+
ev = JSON.parse(line);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (ev.type === 'thread.started' && typeof ev.thread_id === 'string') {
|
|
60
|
+
threadId = ev.thread_id;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (ev.type === 'item.completed' && ev.item) {
|
|
64
|
+
const item = ev.item;
|
|
65
|
+
if (item.type === 'agent_message' && typeof item.text === 'string' && item.text) {
|
|
66
|
+
messages.push(item.text);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (item.type === 'command_execution') {
|
|
70
|
+
const cmd = typeof item.command === 'string' ? item.command : 'command';
|
|
71
|
+
const status = item.status === 'completed' ? '✓' : item.status === 'failed' ? '✗' : '…';
|
|
72
|
+
const trimmed = cmd.length > 80 ? cmd.slice(0, 80) + '…' : cmd;
|
|
73
|
+
messages.push(`\`[${status} ${trimmed}]\``);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (item.type === 'file_change' && Array.isArray(item.changes)) {
|
|
77
|
+
const paths = item.changes.map((c) => c?.path).filter(Boolean).join(', ');
|
|
78
|
+
messages.push(`\`[edit ✓ ${paths}]\``);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Ignore turn.started, turn.completed, and other low-signal events
|
|
83
|
+
}
|
|
84
|
+
return { threadId, messages };
|
|
85
|
+
}
|
|
86
|
+
export function formatCodexOutput(messages) {
|
|
87
|
+
const chunks = [];
|
|
88
|
+
for (const m of messages) {
|
|
89
|
+
chunks.push(...chunkForDiscord(m));
|
|
90
|
+
}
|
|
91
|
+
return chunks;
|
|
92
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CodeAgentWorktreeCleanupResult, CodeAgentWorktreeConfig } from './worktrees.js';
|
|
2
2
|
export interface CodeAgentTask {
|
|
3
3
|
id: string;
|
|
4
4
|
agent: string;
|
|
5
5
|
task: string;
|
|
6
6
|
status: 'running' | 'validating' | 'completed' | 'failed' | 'timeout' | 'pending' | 'cancelled';
|
|
7
7
|
chatId?: number;
|
|
8
|
+
discordThreadId?: string;
|
|
9
|
+
discordChannelId?: string;
|
|
8
10
|
startedAt: string;
|
|
9
11
|
endedAt?: string;
|
|
10
12
|
durationSeconds?: number;
|
|
@@ -15,24 +17,22 @@ export interface CodeAgentTask {
|
|
|
15
17
|
liveOutput?: string;
|
|
16
18
|
error?: string;
|
|
17
19
|
workdir: string;
|
|
20
|
+
sourceWorkdir?: string;
|
|
21
|
+
worktreePath?: string;
|
|
22
|
+
worktreeRef?: string;
|
|
23
|
+
worktreeCleanup?: CodeAgentWorktreeCleanupResult;
|
|
18
24
|
model?: string;
|
|
25
|
+
modelLabel?: string;
|
|
26
|
+
effort?: string;
|
|
19
27
|
retryCount?: number;
|
|
20
28
|
totalCost?: number;
|
|
21
29
|
inputTokens?: number;
|
|
22
30
|
outputTokens?: number;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
subtask?: string;
|
|
26
|
-
synthesisResult?: string;
|
|
27
|
-
dependsOn?: number[];
|
|
28
|
-
wave?: number;
|
|
29
|
-
}
|
|
30
|
-
export interface DecomposedSubtask {
|
|
31
|
-
description: string;
|
|
32
|
-
dependsOn: number[];
|
|
31
|
+
interactive?: boolean;
|
|
32
|
+
cliSessionId?: string;
|
|
33
33
|
}
|
|
34
34
|
export interface CodeAgentBackgroundOptions {
|
|
35
|
-
/** Extra env vars to set
|
|
35
|
+
/** Extra env vars to set on the spawned CLI process */
|
|
36
36
|
env?: Record<string, string>;
|
|
37
37
|
/** Override args builder (returns { cmd, args } instead of buildCodeAgentArgs) */
|
|
38
38
|
buildArgs?: () => {
|
|
@@ -43,31 +43,23 @@ export interface CodeAgentBackgroundOptions {
|
|
|
43
43
|
defaultTimeoutMinutes?: number;
|
|
44
44
|
/** Max timeout in minutes (overrides 30min cap) */
|
|
45
45
|
maxTimeoutMinutes?: number;
|
|
46
|
-
/** Skip sending notification on completion (parent handles it) */
|
|
47
|
-
skipNotification?: boolean;
|
|
48
46
|
/** Per-project validation command overrides from config */
|
|
49
47
|
validationCommands?: Record<string, string>;
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
/** Paths to mount into the sandbox container */
|
|
53
|
-
allowedPaths?: string[];
|
|
48
|
+
/** Worktree cleanup config */
|
|
49
|
+
worktreeConfig?: CodeAgentWorktreeConfig;
|
|
54
50
|
}
|
|
55
51
|
export interface BuildCodeAgentArgsInput {
|
|
56
52
|
task: string;
|
|
57
53
|
agent?: string;
|
|
58
54
|
workdir?: string;
|
|
59
55
|
model?: string;
|
|
56
|
+
effort?: string;
|
|
60
57
|
max_turns?: number;
|
|
58
|
+
sessionId?: string;
|
|
61
59
|
}
|
|
62
60
|
export interface ValidationResult {
|
|
63
61
|
passed: boolean;
|
|
64
62
|
output: string;
|
|
65
63
|
}
|
|
66
|
-
export interface ChildResult {
|
|
67
|
-
subtask: string;
|
|
68
|
-
status: string;
|
|
69
|
-
output?: string;
|
|
70
|
-
error?: string;
|
|
71
|
-
}
|
|
72
64
|
export declare const CODE_AGENT_TIMEOUT_MS: number;
|
|
73
65
|
export declare const VALIDATE_TIMEOUT_MS: number;
|
|
@@ -1,24 +1,42 @@
|
|
|
1
1
|
import type { BuildCodeAgentArgsInput, CodeAgentTask } from './types.js';
|
|
2
2
|
import type { Config } from '../types.js';
|
|
3
|
+
export declare const CLAUDE_CLI_PATH: string;
|
|
4
|
+
/**
|
|
5
|
+
* Prepare env for spawning a coding-agent CLI (claude/codex):
|
|
6
|
+
* - Drops CLAUDECODE so nested `claude` invocations start cleanly.
|
|
7
|
+
* - Drops GH_TOKEN/GITHUB_TOKEN so `gh` falls back to keychain auth instead
|
|
8
|
+
* of a stale process token.
|
|
9
|
+
* - Pins Codex to the standard CLI state directory so auth/config come from ~/.codex.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildCodeAgentSpawnEnv(base?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
3
12
|
/** Return supported coding CLIs currently available on PATH. */
|
|
4
|
-
export declare function getAvailableCodingCliTools(commandChecker?: (name: string) => boolean): Array<'codex' | 'claude'
|
|
13
|
+
export declare function getAvailableCodingCliTools(commandChecker?: (name: string) => boolean): Array<'codex' | 'claude'>;
|
|
5
14
|
/** Return preflight error when no supported coding CLI is installed. */
|
|
6
15
|
export declare function getCodingCliPreflightError(commandChecker?: (name: string) => boolean): string | null;
|
|
7
16
|
/**
|
|
8
17
|
* Normalize legacy/default agent values to supported CLI agent IDs.
|
|
9
|
-
* Accepts strict IDs and older alias-like values (e.g. "claude-
|
|
18
|
+
* Accepts strict IDs and older alias-like values (e.g. "claude-coder").
|
|
10
19
|
*/
|
|
11
|
-
export declare function normalizeCodeAgent(agent: string | undefined): 'claude' | 'codex' |
|
|
20
|
+
export declare function normalizeCodeAgent(agent: string | undefined): 'claude' | 'codex' | null;
|
|
12
21
|
/**
|
|
13
22
|
* Resolve requested/default agent selection to a supported CLI agent ID.
|
|
14
23
|
* Preference order: explicit request -> configured default -> "claude".
|
|
15
|
-
* If no agent is explicit and the model is a GPT/
|
|
16
|
-
|
|
24
|
+
* If no agent is explicit and the model is a GPT/Codex model, auto-select codex.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Check if a model string is compatible with a given agent CLI.
|
|
28
|
+
* e.g. gpt-5.3-codex is NOT compatible with 'claude', claude-opus IS.
|
|
17
29
|
*/
|
|
18
|
-
export declare function
|
|
30
|
+
export declare function isModelCompatibleWithAgent(model: string, agent: 'claude' | 'codex'): boolean;
|
|
31
|
+
export declare function resolveSelectedCodeAgent(requestedAgent: string | undefined, defaultAgent: string | undefined, model?: string): 'claude' | 'codex' | null;
|
|
19
32
|
/** Set the config reference used for auto-notifications on completion. */
|
|
20
33
|
export declare function setCodeAgentConfig(config: Config): void;
|
|
21
34
|
export declare function getCodeAgentConfig(): Config | null;
|
|
35
|
+
/** Read Claude Code's configured default model, if present. */
|
|
36
|
+
export declare function readClaudeCodeDefaultModel(settingsPath?: string): string | undefined;
|
|
37
|
+
/** Resolve the model text shown in dashboards/reports without changing CLI args. */
|
|
38
|
+
export declare function resolveCodeAgentModelLabel(agent: string | undefined, model?: string): string;
|
|
39
|
+
export declare function withCodeAgentModelLabel(task: CodeAgentTask): CodeAgentTask;
|
|
22
40
|
/** Build CLI args for code_with_agent. Exported for testing. */
|
|
23
41
|
export declare function buildCodeAgentArgs(input: BuildCodeAgentArgsInput): {
|
|
24
42
|
cmd: string;
|
|
@@ -26,15 +44,21 @@ export declare function buildCodeAgentArgs(input: BuildCodeAgentArgsInput): {
|
|
|
26
44
|
};
|
|
27
45
|
/** Format duration as human-readable string. */
|
|
28
46
|
export declare function formatDuration(seconds: number | undefined): string;
|
|
29
|
-
/** Build notification for a team-coordinator task with child results. */
|
|
30
|
-
export declare function buildTeamNotification(task: CodeAgentTask, getChildTask: (id: string) => CodeAgentTask | null): string;
|
|
31
47
|
/** Build notification for a single code agent. */
|
|
32
48
|
export declare function buildSoloNotification(task: CodeAgentTask): string;
|
|
49
|
+
interface CodeAgentDiscordNotification {
|
|
50
|
+
content: string;
|
|
51
|
+
attachment?: {
|
|
52
|
+
name: string;
|
|
53
|
+
content: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export declare function buildCodeAgentDiscordNotification(task: CodeAgentTask): CodeAgentDiscordNotification;
|
|
33
58
|
/** Send auto-notification to active channel on completion/failure. */
|
|
34
|
-
export declare function notifyCodeAgentResult(task: CodeAgentTask
|
|
59
|
+
export declare function notifyCodeAgentResult(task: CodeAgentTask): Promise<void>;
|
|
35
60
|
/** Check workdir against allowed paths. */
|
|
36
61
|
export declare function resolveWorkdir(rawWorkdir: string | undefined, projects: Record<string, string>, skimpyclawRoot: string): string;
|
|
37
62
|
/** Resolve model alias to real model ID. */
|
|
38
63
|
export declare function resolveModelAlias(model: string | undefined, aliases: Record<string, string> | undefined): string | undefined;
|
|
39
|
-
|
|
40
|
-
export declare function readTeamState(): null;
|
|
64
|
+
export {};
|