fraim 2.0.159 → 2.0.160
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 +1 -1
- package/dist/src/ai-hub/cert-store.js +70 -0
- package/dist/src/ai-hub/desktop-main.js +225 -50
- package/dist/src/ai-hub/manager-turns.js +38 -0
- package/dist/src/ai-hub/office-sideload.js +138 -0
- package/dist/src/ai-hub/openclaw-bridge.js +239 -0
- package/dist/src/ai-hub/server.js +346 -115
- package/dist/src/ai-hub/word-sideload.js +95 -0
- package/dist/src/cli/commands/add-ide.js +9 -0
- package/dist/src/cli/commands/login.js +1 -2
- package/dist/src/cli/commands/setup.js +0 -2
- package/dist/src/cli/commands/sync.js +19 -10
- package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +66 -2
- package/dist/src/cli/doctor/checks/workflow-checks.js +1 -65
- package/dist/src/cli/mcp/fraim-mcp-latest-launcher.js +136 -0
- package/dist/src/cli/mcp/mcp-server-registry.js +14 -10
- package/dist/src/cli/setup/auto-mcp-setup.js +1 -1
- package/dist/src/cli/utils/fraim-gitignore.js +11 -0
- package/dist/src/cli/utils/remote-sync.js +1 -1
- package/dist/src/core/config-loader.js +1 -2
- package/dist/src/core/fraim-config-schema.generated.js +0 -5
- package/dist/src/core/types.js +0 -1
- package/dist/src/first-run/session-service.js +3 -3
- package/package.json +2 -1
- package/public/ai-hub/index.html +20 -2
- package/public/ai-hub/powerpoint-taskpane/icon-64.png +0 -0
- package/public/ai-hub/powerpoint-taskpane/index.html +235 -0
- package/public/ai-hub/powerpoint-taskpane/manifest.xml +30 -0
- package/public/ai-hub/script.js +337 -120
- package/public/ai-hub/styles.css +456 -135
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.OpenClawAiHubBridge = void 0;
|
|
7
|
+
const express_1 = __importDefault(require("express"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const axios_1 = __importDefault(require("axios"));
|
|
11
|
+
function lastMessage(messages, role) {
|
|
12
|
+
return [...messages].reverse().find((message) => message.role === role);
|
|
13
|
+
}
|
|
14
|
+
function buildStatusLine(run) {
|
|
15
|
+
const status = run.status.toUpperCase();
|
|
16
|
+
const phase = run.currentPhase ? ` | phase: ${run.currentPhase}` : '';
|
|
17
|
+
return `${status} | ${run.jobId} | ${run.hostId}${phase}`;
|
|
18
|
+
}
|
|
19
|
+
function buildStagesLine(run) {
|
|
20
|
+
if (!run.stages?.length)
|
|
21
|
+
return null;
|
|
22
|
+
const line = run.stages
|
|
23
|
+
.map((stage) => {
|
|
24
|
+
const marker = stage.state === 'done' ? '[done]' : stage.state === 'current' ? '[now]' : '[todo]';
|
|
25
|
+
return `${marker} ${stage.label}`;
|
|
26
|
+
})
|
|
27
|
+
.join(' | ');
|
|
28
|
+
return line || null;
|
|
29
|
+
}
|
|
30
|
+
function buildBridgeReply(run) {
|
|
31
|
+
const replyParts = [buildStatusLine(run)];
|
|
32
|
+
const employee = lastMessage(run.messages, 'employee');
|
|
33
|
+
if (employee?.text)
|
|
34
|
+
replyParts.push(employee.text);
|
|
35
|
+
const stages = buildStagesLine(run);
|
|
36
|
+
if (stages)
|
|
37
|
+
replyParts.push(`Stages: ${stages}`);
|
|
38
|
+
return replyParts.join('\n\n');
|
|
39
|
+
}
|
|
40
|
+
class OpenClawAiHubBridge {
|
|
41
|
+
constructor(options) {
|
|
42
|
+
this.app = (0, express_1.default)();
|
|
43
|
+
this.threads = new Map();
|
|
44
|
+
this.api = axios_1.default.create({
|
|
45
|
+
baseURL: options.hubBaseUrl.replace(/\/$/, ''),
|
|
46
|
+
timeout: 5000,
|
|
47
|
+
validateStatus: () => true,
|
|
48
|
+
});
|
|
49
|
+
this.defaultProjectPath = options.defaultProjectPath;
|
|
50
|
+
this.defaultHostId = options.defaultHostId || 'codex';
|
|
51
|
+
this.defaultJobId = options.defaultJobId;
|
|
52
|
+
this.pollingIntervalMs = options.pollingIntervalMs ?? 40;
|
|
53
|
+
this.pollingTimeoutMs = options.pollingTimeoutMs ?? 4000;
|
|
54
|
+
this.threadStatePath = options.threadStatePath ? path_1.default.resolve(options.threadStatePath) : undefined;
|
|
55
|
+
this.missingJobReplyText = options.missingJobReplyText || 'Send /fraim <job-id> <request> to start a FRAIM thread.';
|
|
56
|
+
this.loadThreadState();
|
|
57
|
+
this.app.use(express_1.default.json());
|
|
58
|
+
this.registerRoutes();
|
|
59
|
+
}
|
|
60
|
+
async start(port) {
|
|
61
|
+
if (this.httpServer)
|
|
62
|
+
return;
|
|
63
|
+
await new Promise((resolve) => {
|
|
64
|
+
this.httpServer = this.app.listen(port, '127.0.0.1', () => resolve());
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async stop() {
|
|
68
|
+
if (!this.httpServer)
|
|
69
|
+
return;
|
|
70
|
+
await new Promise((resolve, reject) => {
|
|
71
|
+
this.httpServer?.close((error) => (error ? reject(error) : resolve()));
|
|
72
|
+
});
|
|
73
|
+
this.httpServer = undefined;
|
|
74
|
+
}
|
|
75
|
+
registerRoutes() {
|
|
76
|
+
this.app.get('/api/openclaw-bridge/health', (_req, res) => {
|
|
77
|
+
res.json({ status: 'ok', service: 'openclaw-ai-hub-bridge', threadCount: this.threads.size });
|
|
78
|
+
});
|
|
79
|
+
this.app.get('/api/openclaw-bridge/threads', async (req, res) => {
|
|
80
|
+
try {
|
|
81
|
+
const channelId = String(req.query.channelId || 'telegram').trim();
|
|
82
|
+
const threadId = String(req.query.threadId || '').trim();
|
|
83
|
+
if (!threadId)
|
|
84
|
+
return res.status(400).json({ error: 'threadId is required.' });
|
|
85
|
+
const state = this.threads.get(this.threadKey(channelId, threadId));
|
|
86
|
+
if (!state)
|
|
87
|
+
return res.status(404).json({ error: 'Thread not found.' });
|
|
88
|
+
const run = await this.fetchRun(state.runId);
|
|
89
|
+
return res.json(this.presentThread(state, run));
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return res.status(500).json({ error: error instanceof Error ? error.message : 'Could not load thread.' });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
this.app.post('/api/openclaw-bridge/messages', async (req, res) => {
|
|
96
|
+
try {
|
|
97
|
+
const inbound = this.normalizeInbound(req.body);
|
|
98
|
+
if (!inbound.threadId)
|
|
99
|
+
return res.status(400).json({ error: 'threadId is required.' });
|
|
100
|
+
if (!inbound.text)
|
|
101
|
+
return res.status(400).json({ error: 'text is required.' });
|
|
102
|
+
const key = this.threadKey(inbound.channelId, inbound.threadId);
|
|
103
|
+
let state = this.threads.get(key);
|
|
104
|
+
let run;
|
|
105
|
+
const startedNewRun = !state;
|
|
106
|
+
if (!state) {
|
|
107
|
+
const hostId = inbound.hostId || this.defaultHostId;
|
|
108
|
+
const projectPath = inbound.projectPath || this.defaultProjectPath;
|
|
109
|
+
const jobId = inbound.jobId || this.defaultJobId;
|
|
110
|
+
const start = await this.api.post('/api/ai-hub/runs', {
|
|
111
|
+
projectPath,
|
|
112
|
+
hostId,
|
|
113
|
+
...(jobId ? { jobId } : {}),
|
|
114
|
+
instructions: inbound.text,
|
|
115
|
+
});
|
|
116
|
+
if (start.status !== 201) {
|
|
117
|
+
const errorText = typeof start.data?.error === 'string' ? start.data.error : '';
|
|
118
|
+
if (!jobId && /choose a fraim job/i.test(errorText)) {
|
|
119
|
+
return res.json({
|
|
120
|
+
ok: true,
|
|
121
|
+
startedNewRun: false,
|
|
122
|
+
thread: null,
|
|
123
|
+
outboundMessages: [{ text: this.missingJobReplyText }],
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return res.status(start.status || 400).json(start.data);
|
|
127
|
+
}
|
|
128
|
+
run = start.data;
|
|
129
|
+
state = {
|
|
130
|
+
channelId: inbound.channelId,
|
|
131
|
+
threadId: inbound.threadId,
|
|
132
|
+
runId: run.id,
|
|
133
|
+
hostId,
|
|
134
|
+
jobId: run.jobId,
|
|
135
|
+
projectPath,
|
|
136
|
+
lastSeenAt: new Date().toISOString(),
|
|
137
|
+
};
|
|
138
|
+
this.threads.set(key, state);
|
|
139
|
+
this.persistThreadState();
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const continued = await this.api.post(`/api/ai-hub/runs/${state.runId}/messages`, {
|
|
143
|
+
instructions: inbound.text,
|
|
144
|
+
});
|
|
145
|
+
if (continued.status !== 200) {
|
|
146
|
+
return res.status(continued.status || 400).json(continued.data);
|
|
147
|
+
}
|
|
148
|
+
run = continued.data;
|
|
149
|
+
state.lastSeenAt = new Date().toISOString();
|
|
150
|
+
this.persistThreadState();
|
|
151
|
+
}
|
|
152
|
+
const settled = await this.waitForSettlement(run.id);
|
|
153
|
+
return res.json({
|
|
154
|
+
ok: true,
|
|
155
|
+
startedNewRun,
|
|
156
|
+
thread: this.presentThread(state, settled),
|
|
157
|
+
outboundMessages: [{ text: buildBridgeReply(settled) }],
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
return res.status(500).json({ error: error instanceof Error ? error.message : 'Could not process message.' });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
normalizeInbound(body) {
|
|
166
|
+
const context = body.context || {};
|
|
167
|
+
const metadata = context.metadata || {};
|
|
168
|
+
const metaThreadId = typeof metadata.threadId === 'string' ? metadata.threadId : null;
|
|
169
|
+
const metaChatId = typeof metadata.chatId === 'string' ? metadata.chatId : null;
|
|
170
|
+
const metaSenderId = typeof metadata.senderId === 'string' ? metadata.senderId : null;
|
|
171
|
+
return {
|
|
172
|
+
...body,
|
|
173
|
+
channelId: String(body.channelId || context.channelId || 'telegram').trim(),
|
|
174
|
+
threadId: String(body.threadId || context.threadId || metaThreadId || body.chatId || body.senderId || metaChatId || metaSenderId || '').trim(),
|
|
175
|
+
text: String(body.text || context.content || '').trim(),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
async fetchRun(runId) {
|
|
179
|
+
const response = await this.api.get(`/api/ai-hub/runs/${runId}`);
|
|
180
|
+
if (response.status !== 200) {
|
|
181
|
+
throw new Error(`AI Hub returned ${response.status} while loading run ${runId}.`);
|
|
182
|
+
}
|
|
183
|
+
return response.data;
|
|
184
|
+
}
|
|
185
|
+
async waitForSettlement(runId) {
|
|
186
|
+
const startedAt = Date.now();
|
|
187
|
+
while (Date.now() - startedAt < this.pollingTimeoutMs) {
|
|
188
|
+
const run = await this.fetchRun(runId);
|
|
189
|
+
if (run.status !== 'running')
|
|
190
|
+
return run;
|
|
191
|
+
await new Promise((resolve) => setTimeout(resolve, this.pollingIntervalMs));
|
|
192
|
+
}
|
|
193
|
+
return this.fetchRun(runId);
|
|
194
|
+
}
|
|
195
|
+
presentThread(state, run) {
|
|
196
|
+
return {
|
|
197
|
+
channelId: state.channelId,
|
|
198
|
+
threadId: state.threadId,
|
|
199
|
+
runId: state.runId,
|
|
200
|
+
hostId: state.hostId,
|
|
201
|
+
jobId: state.jobId,
|
|
202
|
+
projectPath: state.projectPath,
|
|
203
|
+
status: run.status,
|
|
204
|
+
currentPhase: run.currentPhase || null,
|
|
205
|
+
stages: run.stages || [],
|
|
206
|
+
totals: run.totals || null,
|
|
207
|
+
latestManagerMessage: lastMessage(run.messages, 'manager')?.text || null,
|
|
208
|
+
latestEmployeeMessage: lastMessage(run.messages, 'employee')?.text || null,
|
|
209
|
+
transcript: run.messages.map((message) => ({
|
|
210
|
+
role: message.role,
|
|
211
|
+
text: message.text,
|
|
212
|
+
createdAt: message.createdAt,
|
|
213
|
+
})),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
threadKey(channelId, threadId) {
|
|
217
|
+
return `${channelId}:${threadId}`;
|
|
218
|
+
}
|
|
219
|
+
loadThreadState() {
|
|
220
|
+
if (!this.threadStatePath || !fs_1.default.existsSync(this.threadStatePath))
|
|
221
|
+
return;
|
|
222
|
+
try {
|
|
223
|
+
const raw = JSON.parse(fs_1.default.readFileSync(this.threadStatePath, 'utf8'));
|
|
224
|
+
for (const thread of raw.threads || []) {
|
|
225
|
+
this.threads.set(this.threadKey(thread.channelId, thread.threadId), thread);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// Ignore corrupt or partial state and start fresh.
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
persistThreadState() {
|
|
233
|
+
if (!this.threadStatePath)
|
|
234
|
+
return;
|
|
235
|
+
fs_1.default.mkdirSync(path_1.default.dirname(this.threadStatePath), { recursive: true });
|
|
236
|
+
fs_1.default.writeFileSync(this.threadStatePath, JSON.stringify({ threads: [...this.threads.values()] }, null, 2), 'utf8');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
exports.OpenClawAiHubBridge = OpenClawAiHubBridge;
|