lazy-gravity 0.0.2 → 0.0.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/LICENSE +21 -0
- package/README.md +224 -0
- package/dist/bin/cli.js +79 -0
- package/dist/bin/commands/doctor.js +156 -0
- package/dist/bin/commands/open.js +145 -0
- package/dist/bin/commands/setup.js +366 -0
- package/dist/bin/commands/start.js +15 -0
- package/dist/bot/index.js +914 -0
- package/dist/commands/chatCommandHandler.js +145 -0
- package/dist/commands/cleanupCommandHandler.js +396 -0
- package/dist/commands/messageParser.js +28 -0
- package/dist/commands/registerSlashCommands.js +149 -0
- package/dist/commands/slashCommandHandler.js +104 -0
- package/dist/commands/workspaceCommandHandler.js +230 -0
- package/dist/database/chatSessionRepository.js +88 -0
- package/dist/database/scheduleRepository.js +119 -0
- package/dist/database/templateRepository.js +103 -0
- package/dist/database/workspaceBindingRepository.js +109 -0
- package/dist/events/interactionCreateHandler.js +286 -0
- package/dist/events/messageCreateHandler.js +154 -0
- package/dist/index.js +10 -0
- package/dist/middleware/auth.js +10 -0
- package/dist/middleware/sanitize.js +20 -0
- package/dist/services/antigravityLauncher.js +89 -0
- package/dist/services/approvalDetector.js +384 -0
- package/dist/services/autoAcceptService.js +80 -0
- package/dist/services/cdpBridgeManager.js +204 -0
- package/dist/services/cdpConnectionPool.js +157 -0
- package/dist/services/cdpService.js +1311 -0
- package/dist/services/channelManager.js +118 -0
- package/dist/services/chatSessionService.js +516 -0
- package/dist/services/modeService.js +73 -0
- package/dist/services/modelService.js +63 -0
- package/dist/services/processManager.js +61 -0
- package/dist/services/progressSender.js +61 -0
- package/dist/services/promptDispatcher.js +17 -0
- package/dist/services/quotaService.js +185 -0
- package/dist/services/responseMonitor.js +645 -0
- package/dist/services/scheduleService.js +134 -0
- package/dist/services/screenshotService.js +85 -0
- package/dist/services/titleGeneratorService.js +113 -0
- package/dist/services/workspaceService.js +64 -0
- package/dist/ui/autoAcceptUi.js +34 -0
- package/dist/ui/modeUi.js +34 -0
- package/dist/ui/modelsUi.js +97 -0
- package/dist/ui/screenshotUi.js +51 -0
- package/dist/ui/templateUi.js +67 -0
- package/dist/utils/cdpPorts.js +5 -0
- package/dist/utils/config.js +20 -0
- package/dist/utils/configLoader.js +160 -0
- package/dist/utils/discordFormatter.js +167 -0
- package/dist/utils/i18n.js +77 -0
- package/dist/utils/imageHandler.js +154 -0
- package/dist/utils/lockfile.js +113 -0
- package/dist/utils/logger.js +32 -0
- package/dist/utils/logo.js +13 -0
- package/dist/utils/metadataExtractor.js +15 -0
- package/dist/utils/processLogBuffer.js +98 -0
- package/dist/utils/streamMessageFormatter.js +90 -0
- package/package.json +73 -5
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TemplateRepository = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Repository class for SQLite persistence of frequently used prompt templates.
|
|
6
|
+
* Handles template creation, retrieval, updating, and deletion.
|
|
7
|
+
*/
|
|
8
|
+
class TemplateRepository {
|
|
9
|
+
db;
|
|
10
|
+
constructor(db) {
|
|
11
|
+
this.db = db;
|
|
12
|
+
this.initialize();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Initialize table (create if not exists)
|
|
16
|
+
*/
|
|
17
|
+
initialize() {
|
|
18
|
+
this.db.exec(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS templates (
|
|
20
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
+
name TEXT NOT NULL UNIQUE,
|
|
22
|
+
prompt TEXT NOT NULL,
|
|
23
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
24
|
+
)
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a new template
|
|
29
|
+
*/
|
|
30
|
+
create(input) {
|
|
31
|
+
const stmt = this.db.prepare(`
|
|
32
|
+
INSERT INTO templates (name, prompt)
|
|
33
|
+
VALUES (?, ?)
|
|
34
|
+
`);
|
|
35
|
+
const result = stmt.run(input.name, input.prompt);
|
|
36
|
+
return {
|
|
37
|
+
id: result.lastInsertRowid,
|
|
38
|
+
name: input.name,
|
|
39
|
+
prompt: input.prompt,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get all templates
|
|
44
|
+
*/
|
|
45
|
+
findAll() {
|
|
46
|
+
const rows = this.db.prepare('SELECT * FROM templates ORDER BY id ASC').all();
|
|
47
|
+
return rows.map(this.mapRow);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Find by ID
|
|
51
|
+
*/
|
|
52
|
+
findById(id) {
|
|
53
|
+
const row = this.db.prepare('SELECT * FROM templates WHERE id = ?').get(id);
|
|
54
|
+
if (!row)
|
|
55
|
+
return undefined;
|
|
56
|
+
return this.mapRow(row);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Find by template name
|
|
60
|
+
*/
|
|
61
|
+
findByName(name) {
|
|
62
|
+
const row = this.db.prepare('SELECT * FROM templates WHERE name = ?').get(name);
|
|
63
|
+
if (!row)
|
|
64
|
+
return undefined;
|
|
65
|
+
return this.mapRow(row);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Delete by template name
|
|
69
|
+
*/
|
|
70
|
+
deleteByName(name) {
|
|
71
|
+
const result = this.db.prepare('DELETE FROM templates WHERE name = ?').run(name);
|
|
72
|
+
return result.changes > 0;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Partially update by template name
|
|
76
|
+
*/
|
|
77
|
+
updateByName(name, input) {
|
|
78
|
+
const sets = [];
|
|
79
|
+
const values = [];
|
|
80
|
+
if (input.prompt !== undefined) {
|
|
81
|
+
sets.push('prompt = ?');
|
|
82
|
+
values.push(input.prompt);
|
|
83
|
+
}
|
|
84
|
+
if (sets.length === 0)
|
|
85
|
+
return false;
|
|
86
|
+
values.push(name);
|
|
87
|
+
const sql = `UPDATE templates SET ${sets.join(', ')} WHERE name = ?`;
|
|
88
|
+
const result = this.db.prepare(sql).run(...values);
|
|
89
|
+
return result.changes > 0;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Map a DB row to TemplateRecord
|
|
93
|
+
*/
|
|
94
|
+
mapRow(row) {
|
|
95
|
+
return {
|
|
96
|
+
id: row.id,
|
|
97
|
+
name: row.name,
|
|
98
|
+
prompt: row.prompt,
|
|
99
|
+
createdAt: row.created_at,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.TemplateRepository = TemplateRepository;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WorkspaceBindingRepository = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Repository for persisting Discord channel to workspace directory bindings in SQLite.
|
|
6
|
+
* Only one workspace can be bound per channel (UNIQUE constraint).
|
|
7
|
+
*/
|
|
8
|
+
class WorkspaceBindingRepository {
|
|
9
|
+
db;
|
|
10
|
+
constructor(db) {
|
|
11
|
+
this.db = db;
|
|
12
|
+
this.initialize();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Initialize table (create if not exists)
|
|
16
|
+
*/
|
|
17
|
+
initialize() {
|
|
18
|
+
this.db.exec(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS workspace_bindings (
|
|
20
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
+
channel_id TEXT NOT NULL UNIQUE,
|
|
22
|
+
workspace_path TEXT NOT NULL,
|
|
23
|
+
guild_id TEXT NOT NULL,
|
|
24
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
25
|
+
)
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Create a new binding
|
|
30
|
+
*/
|
|
31
|
+
create(input) {
|
|
32
|
+
const stmt = this.db.prepare(`
|
|
33
|
+
INSERT INTO workspace_bindings (channel_id, workspace_path, guild_id)
|
|
34
|
+
VALUES (?, ?, ?)
|
|
35
|
+
`);
|
|
36
|
+
const result = stmt.run(input.channelId, input.workspacePath, input.guildId);
|
|
37
|
+
return {
|
|
38
|
+
id: result.lastInsertRowid,
|
|
39
|
+
channelId: input.channelId,
|
|
40
|
+
workspacePath: input.workspacePath,
|
|
41
|
+
guildId: input.guildId,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Find binding by channel ID
|
|
46
|
+
*/
|
|
47
|
+
findByChannelId(channelId) {
|
|
48
|
+
const row = this.db.prepare('SELECT * FROM workspace_bindings WHERE channel_id = ?').get(channelId);
|
|
49
|
+
if (!row)
|
|
50
|
+
return undefined;
|
|
51
|
+
return this.mapRow(row);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Find bindings by workspace path and guild ID
|
|
55
|
+
* Used to prevent duplicate workspace creation
|
|
56
|
+
*/
|
|
57
|
+
findByWorkspacePathAndGuildId(workspacePath, guildId) {
|
|
58
|
+
const rows = this.db.prepare('SELECT * FROM workspace_bindings WHERE workspace_path = ? AND guild_id = ? ORDER BY id ASC').all(workspacePath, guildId);
|
|
59
|
+
return rows.map(this.mapRow);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Find all bindings by guild ID
|
|
63
|
+
*/
|
|
64
|
+
findByGuildId(guildId) {
|
|
65
|
+
const rows = this.db.prepare('SELECT * FROM workspace_bindings WHERE guild_id = ? ORDER BY id ASC').all(guildId);
|
|
66
|
+
return rows.map(this.mapRow);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get all bindings
|
|
70
|
+
*/
|
|
71
|
+
findAll() {
|
|
72
|
+
const rows = this.db.prepare('SELECT * FROM workspace_bindings ORDER BY id ASC').all();
|
|
73
|
+
return rows.map(this.mapRow);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Delete binding by channel ID
|
|
77
|
+
*/
|
|
78
|
+
deleteByChannelId(channelId) {
|
|
79
|
+
const result = this.db.prepare('DELETE FROM workspace_bindings WHERE channel_id = ?').run(channelId);
|
|
80
|
+
return result.changes > 0;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create or update a channel binding (upsert)
|
|
84
|
+
*/
|
|
85
|
+
upsert(input) {
|
|
86
|
+
const stmt = this.db.prepare(`
|
|
87
|
+
INSERT INTO workspace_bindings (channel_id, workspace_path, guild_id)
|
|
88
|
+
VALUES (?, ?, ?)
|
|
89
|
+
ON CONFLICT(channel_id) DO UPDATE SET
|
|
90
|
+
workspace_path = excluded.workspace_path,
|
|
91
|
+
guild_id = excluded.guild_id
|
|
92
|
+
`);
|
|
93
|
+
stmt.run(input.channelId, input.workspacePath, input.guildId);
|
|
94
|
+
return this.findByChannelId(input.channelId);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Map a DB row to WorkspaceBindingRecord
|
|
98
|
+
*/
|
|
99
|
+
mapRow(row) {
|
|
100
|
+
return {
|
|
101
|
+
id: row.id,
|
|
102
|
+
channelId: row.channel_id,
|
|
103
|
+
workspacePath: row.workspace_path,
|
|
104
|
+
guildId: row.guild_id,
|
|
105
|
+
createdAt: row.created_at,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
exports.WorkspaceBindingRepository = WorkspaceBindingRepository;
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createInteractionCreateHandler = createInteractionCreateHandler;
|
|
4
|
+
const discord_js_1 = require("discord.js");
|
|
5
|
+
const i18n_1 = require("../utils/i18n");
|
|
6
|
+
const logger_1 = require("../utils/logger");
|
|
7
|
+
const templateUi_1 = require("../ui/templateUi");
|
|
8
|
+
const autoAcceptUi_1 = require("../ui/autoAcceptUi");
|
|
9
|
+
const cleanupCommandHandler_1 = require("../commands/cleanupCommandHandler");
|
|
10
|
+
const workspaceCommandHandler_1 = require("../commands/workspaceCommandHandler");
|
|
11
|
+
const modeService_1 = require("../services/modeService");
|
|
12
|
+
function createInteractionCreateHandler(deps) {
|
|
13
|
+
return async (interaction) => {
|
|
14
|
+
if (interaction.isButton()) {
|
|
15
|
+
if (!deps.config.allowedUserIds.includes(interaction.user.id)) {
|
|
16
|
+
await interaction.reply({ content: (0, i18n_1.t)('You do not have permission.'), flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const approvalAction = deps.parseApprovalCustomId(interaction.customId);
|
|
21
|
+
if (approvalAction) {
|
|
22
|
+
if (approvalAction.channelId && approvalAction.channelId !== interaction.channelId) {
|
|
23
|
+
await interaction.reply({
|
|
24
|
+
content: (0, i18n_1.t)('This approval action is linked to a different session channel.'),
|
|
25
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
26
|
+
}).catch(logger_1.logger.error);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const workspaceDirName = approvalAction.workspaceDirName ?? deps.bridge.lastActiveWorkspace;
|
|
30
|
+
const detector = workspaceDirName
|
|
31
|
+
? deps.bridge.pool.getApprovalDetector(workspaceDirName)
|
|
32
|
+
: undefined;
|
|
33
|
+
if (!detector) {
|
|
34
|
+
try {
|
|
35
|
+
await interaction.reply({ content: (0, i18n_1.t)('Approval detector not found.'), flags: discord_js_1.MessageFlags.Ephemeral });
|
|
36
|
+
}
|
|
37
|
+
catch { /* ignore */ }
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let success = false;
|
|
41
|
+
let actionLabel = '';
|
|
42
|
+
if (approvalAction.action === 'approve') {
|
|
43
|
+
success = await detector.approveButton();
|
|
44
|
+
actionLabel = (0, i18n_1.t)('Allow');
|
|
45
|
+
}
|
|
46
|
+
else if (approvalAction.action === 'always_allow') {
|
|
47
|
+
success = await detector.alwaysAllowButton();
|
|
48
|
+
actionLabel = (0, i18n_1.t)('Allow Chat');
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
success = await detector.denyButton();
|
|
52
|
+
actionLabel = (0, i18n_1.t)('Deny');
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
if (success) {
|
|
56
|
+
const originalEmbed = interaction.message.embeds[0];
|
|
57
|
+
const updatedEmbed = originalEmbed
|
|
58
|
+
? discord_js_1.EmbedBuilder.from(originalEmbed)
|
|
59
|
+
: new discord_js_1.EmbedBuilder().setTitle('Approval Request');
|
|
60
|
+
const historyText = `${actionLabel} by <@${interaction.user.id}> (${new Date().toLocaleString('ja-JP')})`;
|
|
61
|
+
updatedEmbed
|
|
62
|
+
.setColor(approvalAction.action === 'deny' ? 0xE74C3C : 0x2ECC71)
|
|
63
|
+
.addFields({ name: 'Action History', value: historyText, inline: false })
|
|
64
|
+
.setTimestamp();
|
|
65
|
+
const disabledRows = interaction.message.components
|
|
66
|
+
.map((row) => {
|
|
67
|
+
const rowAny = row;
|
|
68
|
+
if (!Array.isArray(rowAny.components))
|
|
69
|
+
return null;
|
|
70
|
+
const nextRow = new discord_js_1.ActionRowBuilder();
|
|
71
|
+
const disabledButtons = rowAny.components
|
|
72
|
+
.map((component) => {
|
|
73
|
+
const componentType = component?.type ?? component?.data?.type;
|
|
74
|
+
if (componentType !== 2)
|
|
75
|
+
return null;
|
|
76
|
+
const payload = typeof component?.toJSON === 'function'
|
|
77
|
+
? component.toJSON()
|
|
78
|
+
: component;
|
|
79
|
+
return discord_js_1.ButtonBuilder.from(payload).setDisabled(true);
|
|
80
|
+
})
|
|
81
|
+
.filter((button) => button !== null);
|
|
82
|
+
if (disabledButtons.length === 0)
|
|
83
|
+
return null;
|
|
84
|
+
nextRow.addComponents(...disabledButtons);
|
|
85
|
+
return nextRow;
|
|
86
|
+
})
|
|
87
|
+
.filter((row) => row !== null);
|
|
88
|
+
await interaction.update({
|
|
89
|
+
embeds: [updatedEmbed],
|
|
90
|
+
components: disabledRows,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
await interaction.reply({ content: 'Approval button not found.', flags: discord_js_1.MessageFlags.Ephemeral });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (interactionError) {
|
|
98
|
+
if (interactionError?.code === 10062 || interactionError?.code === 40060) {
|
|
99
|
+
logger_1.logger.warn('[Approval] Interaction expired. Responding directly in the channel.');
|
|
100
|
+
if (interaction.channel && 'send' in interaction.channel) {
|
|
101
|
+
const fallbackMessage = success
|
|
102
|
+
? `${actionLabel} completed.`
|
|
103
|
+
: 'Approval button not found.';
|
|
104
|
+
await interaction.channel.send(fallbackMessage).catch(logger_1.logger.error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
throw interactionError;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (interaction.customId === cleanupCommandHandler_1.CLEANUP_ARCHIVE_BTN) {
|
|
114
|
+
await deps.cleanupHandler.handleArchive(interaction);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (interaction.customId === cleanupCommandHandler_1.CLEANUP_DELETE_BTN) {
|
|
118
|
+
await deps.cleanupHandler.handleDelete(interaction);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (interaction.customId === cleanupCommandHandler_1.CLEANUP_CANCEL_BTN) {
|
|
122
|
+
await deps.cleanupHandler.handleCancel(interaction);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (interaction.customId === 'model_refresh_btn') {
|
|
126
|
+
await interaction.deferUpdate();
|
|
127
|
+
await deps.sendModelsUI({ editReply: async (data) => await interaction.editReply(data) }, {
|
|
128
|
+
getCurrentCdp: () => deps.getCurrentCdp(deps.bridge),
|
|
129
|
+
fetchQuota: async () => deps.bridge.quota.fetchQuota(),
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (interaction.customId.startsWith('model_btn_')) {
|
|
134
|
+
await interaction.deferUpdate();
|
|
135
|
+
const modelName = interaction.customId.replace('model_btn_', '');
|
|
136
|
+
const cdp = deps.getCurrentCdp(deps.bridge);
|
|
137
|
+
if (!cdp) {
|
|
138
|
+
await interaction.followUp({ content: 'Not connected to CDP.', flags: discord_js_1.MessageFlags.Ephemeral });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const res = await cdp.setUiModel(modelName);
|
|
142
|
+
if (!res.ok) {
|
|
143
|
+
await interaction.followUp({ content: res.error || 'Failed to change model.', flags: discord_js_1.MessageFlags.Ephemeral });
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
await deps.sendModelsUI({ editReply: async (data) => await interaction.editReply(data) }, {
|
|
147
|
+
getCurrentCdp: () => deps.getCurrentCdp(deps.bridge),
|
|
148
|
+
fetchQuota: async () => deps.bridge.quota.fetchQuota(),
|
|
149
|
+
});
|
|
150
|
+
await interaction.followUp({ content: `Model changed to **${res.model}**!`, flags: discord_js_1.MessageFlags.Ephemeral });
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (interaction.customId === autoAcceptUi_1.AUTOACCEPT_BTN_REFRESH) {
|
|
155
|
+
await interaction.deferUpdate();
|
|
156
|
+
await deps.sendAutoAcceptUI({ editReply: async (data) => await interaction.editReply(data) }, deps.bridge.autoAccept);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (interaction.customId === autoAcceptUi_1.AUTOACCEPT_BTN_ON || interaction.customId === autoAcceptUi_1.AUTOACCEPT_BTN_OFF) {
|
|
160
|
+
await interaction.deferUpdate();
|
|
161
|
+
const action = interaction.customId === autoAcceptUi_1.AUTOACCEPT_BTN_ON ? 'on' : 'off';
|
|
162
|
+
const result = deps.bridge.autoAccept.handle(action);
|
|
163
|
+
await deps.sendAutoAcceptUI({ editReply: async (data) => await interaction.editReply(data) }, deps.bridge.autoAccept);
|
|
164
|
+
await interaction.followUp({
|
|
165
|
+
content: result.message,
|
|
166
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (interaction.customId.startsWith(templateUi_1.TEMPLATE_BTN_PREFIX)) {
|
|
171
|
+
await interaction.deferUpdate();
|
|
172
|
+
const templateId = (0, templateUi_1.parseTemplateButtonId)(interaction.customId);
|
|
173
|
+
if (!isNaN(templateId) && deps.handleTemplateUse) {
|
|
174
|
+
await deps.handleTemplateUse(interaction, templateId);
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
logger_1.logger.error('Error during button interaction handling:', error);
|
|
181
|
+
try {
|
|
182
|
+
if (!interaction.replied && !interaction.deferred) {
|
|
183
|
+
await interaction.reply({ content: 'An error occurred while processing the button action.', flags: discord_js_1.MessageFlags.Ephemeral });
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
await interaction.followUp({ content: 'An error occurred while processing the button action.', flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
logger_1.logger.error('Failed to send error message as well:', e);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (interaction.isStringSelectMenu() && interaction.customId === 'mode_select') {
|
|
195
|
+
if (!deps.config.allowedUserIds.includes(interaction.user.id)) {
|
|
196
|
+
await interaction.reply({ content: (0, i18n_1.t)('You do not have permission.'), flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
await interaction.deferUpdate();
|
|
201
|
+
}
|
|
202
|
+
catch (deferError) {
|
|
203
|
+
if (deferError?.code === 10062 || deferError?.code === 40060) {
|
|
204
|
+
logger_1.logger.warn('[Mode] deferUpdate expired. Skipping.');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
logger_1.logger.error('[Mode] deferUpdate failed:', deferError);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
const selectedMode = interaction.values[0];
|
|
212
|
+
deps.modeService.setMode(selectedMode);
|
|
213
|
+
const cdp = deps.getCurrentCdp(deps.bridge);
|
|
214
|
+
if (cdp) {
|
|
215
|
+
const res = await cdp.setUiMode(selectedMode);
|
|
216
|
+
if (!res.ok) {
|
|
217
|
+
logger_1.logger.warn(`[Mode] UI mode switch failed: ${res.error}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
await deps.sendModeUI({ editReply: async (data) => await interaction.editReply(data) }, deps.modeService);
|
|
221
|
+
await interaction.followUp({ content: `Mode changed to **${modeService_1.MODE_DISPLAY_NAMES[selectedMode] || selectedMode}**!`, flags: discord_js_1.MessageFlags.Ephemeral });
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
logger_1.logger.error('Error during mode dropdown handling:', error);
|
|
225
|
+
try {
|
|
226
|
+
if (interaction.deferred || interaction.replied) {
|
|
227
|
+
await interaction.followUp({ content: 'An error occurred while changing the mode.', flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch (e) {
|
|
231
|
+
logger_1.logger.error('Failed to send error message:', e);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (interaction.isStringSelectMenu() && (interaction.customId === workspaceCommandHandler_1.PROJECT_SELECT_ID || interaction.customId === workspaceCommandHandler_1.WORKSPACE_SELECT_ID)) {
|
|
237
|
+
if (!deps.config.allowedUserIds.includes(interaction.user.id)) {
|
|
238
|
+
await interaction.reply({ content: (0, i18n_1.t)('You do not have permission.'), flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (!interaction.guild) {
|
|
242
|
+
await interaction.reply({ content: 'This can only be used in a server.', flags: discord_js_1.MessageFlags.Ephemeral }).catch(logger_1.logger.error);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
await deps.wsHandler.handleSelectMenu(interaction, interaction.guild);
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
logger_1.logger.error('Workspace selection error:', error);
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (!interaction.isChatInputCommand())
|
|
254
|
+
return;
|
|
255
|
+
const commandInteraction = interaction;
|
|
256
|
+
if (!deps.config.allowedUserIds.includes(interaction.user.id)) {
|
|
257
|
+
await commandInteraction.reply({
|
|
258
|
+
content: 'You do not have permission to use this command.',
|
|
259
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
260
|
+
}).catch(logger_1.logger.error);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
await commandInteraction.deferReply();
|
|
265
|
+
}
|
|
266
|
+
catch (deferError) {
|
|
267
|
+
if (deferError?.code === 10062) {
|
|
268
|
+
logger_1.logger.warn('[SlashCommand] Interaction expired (deferReply failed). Skipping.');
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
throw deferError;
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
await deps.handleSlashInteraction(commandInteraction, deps.slashCommandHandler, deps.bridge, deps.wsHandler, deps.chatHandler, deps.cleanupHandler, deps.modeService, deps.modelService, deps.bridge.autoAccept, deps.client);
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
logger_1.logger.error('Error during slash command handling:', error);
|
|
278
|
+
try {
|
|
279
|
+
await commandInteraction.editReply({ content: 'An error occurred while processing the command.' });
|
|
280
|
+
}
|
|
281
|
+
catch (replyError) {
|
|
282
|
+
logger_1.logger.error('Failed to send error response:', replyError);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMessageCreateHandler = createMessageCreateHandler;
|
|
4
|
+
const discord_js_1 = require("discord.js");
|
|
5
|
+
const messageParser_1 = require("../commands/messageParser");
|
|
6
|
+
const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
|
|
7
|
+
const modeService_1 = require("../services/modeService");
|
|
8
|
+
const imageHandler_1 = require("../utils/imageHandler");
|
|
9
|
+
const logger_1 = require("../utils/logger");
|
|
10
|
+
function createMessageCreateHandler(deps) {
|
|
11
|
+
const getCurrentCdp = deps.getCurrentCdp ?? cdpBridgeManager_1.getCurrentCdp;
|
|
12
|
+
const ensureApprovalDetector = deps.ensureApprovalDetector ?? cdpBridgeManager_1.ensureApprovalDetector;
|
|
13
|
+
const registerApprovalWorkspaceChannel = deps.registerApprovalWorkspaceChannel ?? cdpBridgeManager_1.registerApprovalWorkspaceChannel;
|
|
14
|
+
const registerApprovalSessionChannel = deps.registerApprovalSessionChannel ?? cdpBridgeManager_1.registerApprovalSessionChannel;
|
|
15
|
+
const downloadInboundImageAttachments = deps.downloadInboundImageAttachments ?? imageHandler_1.downloadInboundImageAttachments;
|
|
16
|
+
const cleanupInboundImageAttachments = deps.cleanupInboundImageAttachments ?? imageHandler_1.cleanupInboundImageAttachments;
|
|
17
|
+
const isImageAttachment = deps.isImageAttachment ?? imageHandler_1.isImageAttachment;
|
|
18
|
+
return async (message) => {
|
|
19
|
+
if (message.author.bot)
|
|
20
|
+
return;
|
|
21
|
+
if (!deps.config.allowedUserIds.includes(message.author.id)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const parsed = (0, messageParser_1.parseMessageContent)(message.content);
|
|
25
|
+
if (parsed.isCommand && parsed.commandName) {
|
|
26
|
+
if (parsed.commandName === 'autoaccept') {
|
|
27
|
+
const result = deps.bridge.autoAccept.handle(parsed.args?.[0]);
|
|
28
|
+
await message.reply({ content: result.message }).catch(logger_1.logger.error);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (parsed.commandName === 'screenshot') {
|
|
32
|
+
await deps.handleScreenshot(message, getCurrentCdp(deps.bridge));
|
|
33
|
+
await message.reply({ content: '💡 You can also use the slash command `/screenshot`.' }).catch(() => { });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (parsed.commandName === 'status') {
|
|
37
|
+
const activeNames = deps.bridge.pool.getActiveWorkspaceNames();
|
|
38
|
+
const currentMode = deps.modeService.getCurrentMode();
|
|
39
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
40
|
+
.setTitle('🔧 Bot Status')
|
|
41
|
+
.setColor(activeNames.length > 0 ? 0x00CC88 : 0x888888)
|
|
42
|
+
.addFields({ name: 'CDP Connection', value: activeNames.length > 0 ? `🟢 ${activeNames.length} project(s) connected` : '⚪ Disconnected', inline: true }, { name: 'Mode', value: modeService_1.MODE_DISPLAY_NAMES[currentMode] || currentMode, inline: true }, { name: 'Auto Approve', value: deps.bridge.autoAccept.isEnabled() ? '🟢 ON' : '⚪ OFF', inline: true })
|
|
43
|
+
.setFooter({ text: '💡 Use the slash command /status for more detailed information' })
|
|
44
|
+
.setTimestamp();
|
|
45
|
+
if (activeNames.length > 0) {
|
|
46
|
+
const lines = activeNames.map((name) => {
|
|
47
|
+
const cdp = deps.bridge.pool.getConnected(name);
|
|
48
|
+
const contexts = cdp ? cdp.getContexts().length : 0;
|
|
49
|
+
const detectorActive = deps.bridge.pool.getApprovalDetector(name)?.isActive() ? ' [Detecting]' : '';
|
|
50
|
+
return `• **${name}** — Contexts: ${contexts}${detectorActive}`;
|
|
51
|
+
});
|
|
52
|
+
embed.setDescription(`**Connected Projects:**\n${lines.join('\n')}`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
embed.setDescription('Send a message to auto-connect to a project.');
|
|
56
|
+
}
|
|
57
|
+
await message.reply({ embeds: [embed] });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const slashOnlyCommands = ['help', 'stop', 'model', 'mode', 'project', 'chat', 'new', 'cleanup'];
|
|
61
|
+
if (slashOnlyCommands.includes(parsed.commandName)) {
|
|
62
|
+
await message.reply({
|
|
63
|
+
content: `💡 Please use \`/${parsed.commandName}\` as a slash command.\nType \`/${parsed.commandName}\` in the Discord input field to see suggestions.`,
|
|
64
|
+
}).catch(logger_1.logger.error);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const result = await deps.slashCommandHandler.handleCommand(parsed.commandName, parsed.args || []);
|
|
68
|
+
await message.reply({
|
|
69
|
+
content: result.message,
|
|
70
|
+
}).catch(logger_1.logger.error);
|
|
71
|
+
if (result.prompt) {
|
|
72
|
+
const cdp = getCurrentCdp(deps.bridge);
|
|
73
|
+
if (cdp) {
|
|
74
|
+
await deps.sendPromptToAntigravity(deps.bridge, message, result.prompt, cdp, deps.modeService, deps.modelService, [], {
|
|
75
|
+
chatSessionService: deps.chatSessionService,
|
|
76
|
+
chatSessionRepo: deps.chatSessionRepo,
|
|
77
|
+
channelManager: deps.channelManager,
|
|
78
|
+
titleGenerator: deps.titleGenerator,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
await message.reply('Not connected to CDP. Send a message first to connect to a project.');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const hasImageAttachments = Array.from(message.attachments.values())
|
|
88
|
+
.some((attachment) => isImageAttachment(attachment.contentType, attachment.name));
|
|
89
|
+
if (message.content.trim() || hasImageAttachments) {
|
|
90
|
+
const promptText = message.content.trim() || 'Please review the attached images and respond accordingly.';
|
|
91
|
+
const inboundImages = await downloadInboundImageAttachments(message);
|
|
92
|
+
if (hasImageAttachments && inboundImages.length === 0) {
|
|
93
|
+
await message.reply('Failed to retrieve attached images. Please wait and try again.').catch(() => { });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const workspacePath = deps.wsHandler.getWorkspaceForChannel(message.channelId);
|
|
97
|
+
try {
|
|
98
|
+
if (workspacePath) {
|
|
99
|
+
try {
|
|
100
|
+
const cdp = await deps.bridge.pool.getOrConnect(workspacePath);
|
|
101
|
+
const dirName = deps.bridge.pool.extractDirName(workspacePath);
|
|
102
|
+
deps.bridge.lastActiveWorkspace = dirName;
|
|
103
|
+
deps.bridge.lastActiveChannel = message.channel;
|
|
104
|
+
registerApprovalWorkspaceChannel(deps.bridge, dirName, message.channel);
|
|
105
|
+
ensureApprovalDetector(deps.bridge, cdp, dirName, deps.client);
|
|
106
|
+
const session = deps.chatSessionRepo.findByChannelId(message.channelId);
|
|
107
|
+
if (session?.displayName) {
|
|
108
|
+
registerApprovalSessionChannel(deps.bridge, dirName, session.displayName, message.channel);
|
|
109
|
+
}
|
|
110
|
+
if (session?.isRenamed && session.displayName) {
|
|
111
|
+
const activationResult = await deps.chatSessionService.activateSessionByTitle(cdp, session.displayName);
|
|
112
|
+
if (!activationResult.ok) {
|
|
113
|
+
const reason = activationResult.error ? ` (${activationResult.error})` : '';
|
|
114
|
+
await message.reply(`⚠️ Could not route this message to the bound session (${session.displayName}). ` +
|
|
115
|
+
`Please open /chat and verify the session${reason}.`).catch(() => { });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (session && !session.isRenamed) {
|
|
120
|
+
try {
|
|
121
|
+
const chatResult = await deps.chatSessionService.startNewChat(cdp);
|
|
122
|
+
if (!chatResult.ok) {
|
|
123
|
+
logger_1.logger.warn('[MessageCreate] Failed to start new chat in Antigravity:', chatResult.error);
|
|
124
|
+
message.channel.send(`⚠️ Could not open a new chat in Antigravity. Sending to existing chat.`).catch(() => { });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
logger_1.logger.error('[MessageCreate] startNewChat error:', err);
|
|
129
|
+
message.channel.send(`⚠️ Could not open a new chat in Antigravity. Sending to existing chat.`).catch(() => { });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
await deps.autoRenameChannel(message, deps.chatSessionRepo, deps.titleGenerator, deps.channelManager, cdp);
|
|
133
|
+
await deps.sendPromptToAntigravity(deps.bridge, message, promptText, cdp, deps.modeService, deps.modelService, inboundImages, {
|
|
134
|
+
chatSessionService: deps.chatSessionService,
|
|
135
|
+
chatSessionRepo: deps.chatSessionRepo,
|
|
136
|
+
channelManager: deps.channelManager,
|
|
137
|
+
titleGenerator: deps.titleGenerator,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
await message.reply(`Failed to connect to workspace: ${e.message}`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
await message.reply('No project is configured for this channel. Please create or select one with `/project`.');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
await cleanupInboundImageAttachments(inboundImages);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const logger_1 = require("./utils/logger");
|
|
4
|
+
const bot_1 = require("./bot");
|
|
5
|
+
const lockfile_1 = require("./utils/lockfile");
|
|
6
|
+
const logo_1 = require("./utils/logo");
|
|
7
|
+
console.log(logo_1.LOGO);
|
|
8
|
+
// Prevent duplicate launch: exit immediately if bot is already running
|
|
9
|
+
(0, lockfile_1.acquireLock)();
|
|
10
|
+
(0, bot_1.startBot)().catch(logger_1.logger.error);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withAuth = void 0;
|
|
4
|
+
const withAuth = (userId, allowedUserIds, next) => {
|
|
5
|
+
if (allowedUserIds.includes(userId)) {
|
|
6
|
+
next();
|
|
7
|
+
}
|
|
8
|
+
return;
|
|
9
|
+
};
|
|
10
|
+
exports.withAuth = withAuth;
|