fraim 2.0.203 → 2.0.205
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 +3 -3
- package/bin/fraim.js +8 -18
- package/dist/src/cli/commands/first-run.js +2 -37
- package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +1 -1
- package/dist/src/cli/fraim.js +0 -2
- package/dist/src/cli/setup/ide-invocation-surfaces.js +6 -6
- package/dist/src/cli/utils/managed-agent-paths.js +76 -6
- package/dist/src/cli/utils/version-utils.js +2 -2
- package/dist/src/config/persona-capability-bundles.js +3 -3
- package/dist/src/core/job-phases.js +168 -0
- package/dist/src/core/utils/ports.js +32 -0
- package/dist/src/core/utils/stub-generator.js +24 -2
- package/dist/src/first-run/session-service.js +106 -24
- package/dist/src/local-mcp-server/learning-context-builder.js +1 -1
- package/dist/src/local-mcp-server/stdio-server.js +41 -9
- package/dist/src/routes/analytics.js +2 -2
- package/dist/src/services/registry-service.js +2 -2
- package/index.js +1 -1
- package/package.json +61 -181
- package/dist/src/ai-hub/catalog.js +0 -406
- package/dist/src/ai-hub/cert-store.js +0 -70
- package/dist/src/ai-hub/conversation-store.js +0 -359
- package/dist/src/ai-hub/desktop-main.js +0 -449
- package/dist/src/ai-hub/hosts.js +0 -1664
- package/dist/src/ai-hub/hub-latest-version.js +0 -52
- package/dist/src/ai-hub/hub-launch-decision.js +0 -57
- package/dist/src/ai-hub/hub-runtime-file.js +0 -43
- package/dist/src/ai-hub/managed-browser.js +0 -269
- package/dist/src/ai-hub/manager-turns.js +0 -64
- package/dist/src/ai-hub/office-sideload.js +0 -156
- package/dist/src/ai-hub/openclaw-bridge.js +0 -250
- package/dist/src/ai-hub/preferences.js +0 -201
- package/dist/src/ai-hub/remote-hub-gateway.js +0 -88
- package/dist/src/ai-hub/server.js +0 -3827
- package/dist/src/ai-hub/types.js +0 -2
- package/dist/src/ai-hub/word-sideload.js +0 -95
- package/dist/src/cli/commands/hub.js +0 -238
- package/dist/src/cli/commands/test-mcp.js +0 -171
- package/dist/src/cli/setup/first-run.js +0 -242
- package/dist/src/core/config-writer.js +0 -75
- package/dist/src/core/utils/job-aliases.js +0 -47
- package/dist/src/core/utils/workflow-parser.js +0 -174
- package/dist/src/services/email-service-clean.js +0 -782
- package/extensions/office-word/favicon.ico +0 -0
- package/extensions/office-word/icon-64.png +0 -0
- package/extensions/office-word/manifest.xml +0 -33
- package/extensions/office-word/taskpane.html +0 -242
- package/public/ai-hub/index.html +0 -894
- package/public/ai-hub/powerpoint-taskpane/icon-64.png +0 -0
- package/public/ai-hub/powerpoint-taskpane/index.html +0 -236
- package/public/ai-hub/powerpoint-taskpane/manifest.xml +0 -30
- package/public/ai-hub/review.css +0 -381
- package/public/ai-hub/script.js +0 -11015
- package/public/ai-hub/styles.css +0 -4873
- package/public/portfolio/ashley.html +0 -523
- package/public/portfolio/auditya.html +0 -83
- package/public/portfolio/banke.html +0 -83
- package/public/portfolio/beza.html +0 -659
- package/public/portfolio/careena.html +0 -632
- package/public/portfolio/casey.html +0 -568
- package/public/portfolio/celia.html +0 -490
- package/public/portfolio/deidre.html +0 -642
- package/public/portfolio/gautam.html +0 -597
- package/public/portfolio/hari.html +0 -469
- package/public/portfolio/huxley.html +0 -1354
- package/public/portfolio/index.html +0 -741
- package/public/portfolio/maestro.html +0 -518
- package/public/portfolio/mandy.html +0 -590
- package/public/portfolio/mona.html +0 -597
- package/public/portfolio/pam.html +0 -887
- package/public/portfolio/procella.html +0 -107
- package/public/portfolio/qasm.html +0 -569
- package/public/portfolio/ricardo.html +0 -489
- package/public/portfolio/sade.html +0 -560
- package/public/portfolio/sam.html +0 -654
- package/public/portfolio/sechar.html +0 -580
- package/public/portfolio/sreya.html +0 -599
- package/public/portfolio/swen.html +0 -601
- /package/dist/src/{ai-hub → core}/brand-store.js +0 -0
|
@@ -1,250 +0,0 @@
|
|
|
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
|
-
managerTexts: [inbound.text],
|
|
138
|
-
};
|
|
139
|
-
this.threads.set(key, state);
|
|
140
|
-
this.persistThreadState();
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
const continued = await this.api.post(`/api/ai-hub/runs/${state.runId}/messages`, {
|
|
144
|
-
instructions: inbound.text,
|
|
145
|
-
});
|
|
146
|
-
if (continued.status !== 200) {
|
|
147
|
-
return res.status(continued.status || 400).json(continued.data);
|
|
148
|
-
}
|
|
149
|
-
run = continued.data;
|
|
150
|
-
state.lastSeenAt = new Date().toISOString();
|
|
151
|
-
state.managerTexts = [...(state.managerTexts ?? []), inbound.text];
|
|
152
|
-
this.persistThreadState();
|
|
153
|
-
}
|
|
154
|
-
const settled = await this.waitForSettlement(run.id);
|
|
155
|
-
return res.json({
|
|
156
|
-
ok: true,
|
|
157
|
-
startedNewRun,
|
|
158
|
-
thread: this.presentThread(state, settled),
|
|
159
|
-
outboundMessages: [{ text: buildBridgeReply(settled) }],
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
catch (error) {
|
|
163
|
-
return res.status(500).json({ error: error instanceof Error ? error.message : 'Could not process message.' });
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
normalizeInbound(body) {
|
|
168
|
-
const context = body.context || {};
|
|
169
|
-
const metadata = context.metadata || {};
|
|
170
|
-
const metaThreadId = typeof metadata.threadId === 'string' ? metadata.threadId : null;
|
|
171
|
-
const metaChatId = typeof metadata.chatId === 'string' ? metadata.chatId : null;
|
|
172
|
-
const metaSenderId = typeof metadata.senderId === 'string' ? metadata.senderId : null;
|
|
173
|
-
return {
|
|
174
|
-
...body,
|
|
175
|
-
channelId: String(body.channelId || context.channelId || 'telegram').trim(),
|
|
176
|
-
threadId: String(body.threadId || context.threadId || metaThreadId || body.chatId || body.senderId || metaChatId || metaSenderId || '').trim(),
|
|
177
|
-
text: String(body.text || context.content || '').trim(),
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
async fetchRun(runId) {
|
|
181
|
-
const response = await this.api.get(`/api/ai-hub/runs/${runId}`);
|
|
182
|
-
if (response.status !== 200) {
|
|
183
|
-
throw new Error(`AI Hub returned ${response.status} while loading run ${runId}.`);
|
|
184
|
-
}
|
|
185
|
-
return response.data;
|
|
186
|
-
}
|
|
187
|
-
async waitForSettlement(runId) {
|
|
188
|
-
const startedAt = Date.now();
|
|
189
|
-
while (Date.now() - startedAt < this.pollingTimeoutMs) {
|
|
190
|
-
const run = await this.fetchRun(runId);
|
|
191
|
-
if (run.status !== 'running')
|
|
192
|
-
return run;
|
|
193
|
-
await new Promise((resolve) => setTimeout(resolve, this.pollingIntervalMs));
|
|
194
|
-
}
|
|
195
|
-
return this.fetchRun(runId);
|
|
196
|
-
}
|
|
197
|
-
presentThread(state, run) {
|
|
198
|
-
// The Hub records the invocation it sent as each manager bubble (#696). For a channel
|
|
199
|
-
// transcript we substitute the user's own words (state.managerTexts, in turn order)
|
|
200
|
-
// for manager-role messages, so the thread reflects what the user actually typed.
|
|
201
|
-
const managerTexts = state.managerTexts ?? [];
|
|
202
|
-
let managerIdx = 0;
|
|
203
|
-
const transcript = run.messages.map((message) => ({
|
|
204
|
-
role: message.role,
|
|
205
|
-
text: message.role === 'manager' && managerIdx < managerTexts.length
|
|
206
|
-
? managerTexts[managerIdx++]
|
|
207
|
-
: message.text,
|
|
208
|
-
createdAt: message.createdAt,
|
|
209
|
-
}));
|
|
210
|
-
const latestManagerMessage = [...transcript].reverse().find((m) => m.role === 'manager')?.text || null;
|
|
211
|
-
return {
|
|
212
|
-
channelId: state.channelId,
|
|
213
|
-
threadId: state.threadId,
|
|
214
|
-
runId: state.runId,
|
|
215
|
-
hostId: state.hostId,
|
|
216
|
-
jobId: state.jobId,
|
|
217
|
-
projectPath: state.projectPath,
|
|
218
|
-
status: run.status,
|
|
219
|
-
currentPhase: run.currentPhase || null,
|
|
220
|
-
stages: run.stages || [],
|
|
221
|
-
totals: run.totals || null,
|
|
222
|
-
latestManagerMessage,
|
|
223
|
-
latestEmployeeMessage: lastMessage(run.messages, 'employee')?.text || null,
|
|
224
|
-
transcript,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
threadKey(channelId, threadId) {
|
|
228
|
-
return `${channelId}:${threadId}`;
|
|
229
|
-
}
|
|
230
|
-
loadThreadState() {
|
|
231
|
-
if (!this.threadStatePath || !fs_1.default.existsSync(this.threadStatePath))
|
|
232
|
-
return;
|
|
233
|
-
try {
|
|
234
|
-
const raw = JSON.parse(fs_1.default.readFileSync(this.threadStatePath, 'utf8'));
|
|
235
|
-
for (const thread of raw.threads || []) {
|
|
236
|
-
this.threads.set(this.threadKey(thread.channelId, thread.threadId), thread);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
catch {
|
|
240
|
-
// Ignore corrupt or partial state and start fresh.
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
persistThreadState() {
|
|
244
|
-
if (!this.threadStatePath)
|
|
245
|
-
return;
|
|
246
|
-
fs_1.default.mkdirSync(path_1.default.dirname(this.threadStatePath), { recursive: true });
|
|
247
|
-
fs_1.default.writeFileSync(this.threadStatePath, JSON.stringify({ threads: [...this.threads.values()] }, null, 2), 'utf8');
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
exports.OpenClawAiHubBridge = OpenClawAiHubBridge;
|
|
@@ -1,201 +0,0 @@
|
|
|
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.AiHubPreferencesStore = void 0;
|
|
7
|
-
exports.normalizeAiHubProjectList = normalizeAiHubProjectList;
|
|
8
|
-
const fs_1 = __importDefault(require("fs"));
|
|
9
|
-
const crypto_1 = require("crypto");
|
|
10
|
-
const path_1 = __importDefault(require("path"));
|
|
11
|
-
const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
|
|
12
|
-
const DEFAULT_CATEGORY = 'marketing';
|
|
13
|
-
const DEFAULT_EMPLOYEE = 'claude';
|
|
14
|
-
const defaultPreferences = (projectPath) => ({
|
|
15
|
-
projectPath,
|
|
16
|
-
employeeId: DEFAULT_EMPLOYEE,
|
|
17
|
-
categoryId: DEFAULT_CATEGORY,
|
|
18
|
-
recentJobIds: [],
|
|
19
|
-
recentJobInstructions: {},
|
|
20
|
-
personaKey: null,
|
|
21
|
-
projects: normalizeAiHubProjectList([], projectPath),
|
|
22
|
-
removedProjectPaths: [],
|
|
23
|
-
});
|
|
24
|
-
function normalizeProjectPath(projectPath) {
|
|
25
|
-
return path_1.default.resolve(projectPath || process.cwd());
|
|
26
|
-
}
|
|
27
|
-
function canonicalProjectPath(projectPath) {
|
|
28
|
-
const normalized = normalizeProjectPath(projectPath);
|
|
29
|
-
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
30
|
-
}
|
|
31
|
-
function projectPathExists(projectPath) {
|
|
32
|
-
try {
|
|
33
|
-
return fs_1.default.statSync(projectPath).isDirectory();
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function projectIdForPath(projectPath) {
|
|
40
|
-
return `p-${(0, crypto_1.createHash)('sha1').update(canonicalProjectPath(projectPath)).digest('base64url').slice(0, 16)}`;
|
|
41
|
-
}
|
|
42
|
-
function normalizeRemovedProjectPaths(raw, currentProjectPath) {
|
|
43
|
-
if (!Array.isArray(raw))
|
|
44
|
-
return [];
|
|
45
|
-
const currentKey = currentProjectPath ? canonicalProjectPath(currentProjectPath) : null;
|
|
46
|
-
const seen = new Set();
|
|
47
|
-
for (const value of raw) {
|
|
48
|
-
if (typeof value !== 'string' || value.trim().length === 0)
|
|
49
|
-
continue;
|
|
50
|
-
const key = canonicalProjectPath(value);
|
|
51
|
-
if (currentKey && key === currentKey)
|
|
52
|
-
continue;
|
|
53
|
-
seen.add(key);
|
|
54
|
-
}
|
|
55
|
-
return Array.from(seen);
|
|
56
|
-
}
|
|
57
|
-
function uniqueProjectId(entry, seenIds) {
|
|
58
|
-
const fallbackId = projectIdForPath(entry.folderPath);
|
|
59
|
-
const rawId = typeof entry.id === 'string' ? entry.id.trim() : '';
|
|
60
|
-
const preferredId = rawId && rawId !== 'p-current' ? rawId : fallbackId;
|
|
61
|
-
let id = preferredId;
|
|
62
|
-
let suffix = 2;
|
|
63
|
-
while (seenIds.has(id)) {
|
|
64
|
-
id = `${fallbackId}-${suffix}`;
|
|
65
|
-
suffix += 1;
|
|
66
|
-
}
|
|
67
|
-
seenIds.add(id);
|
|
68
|
-
return id;
|
|
69
|
-
}
|
|
70
|
-
function withUniqueProjectIds(projects) {
|
|
71
|
-
const seenIds = new Set();
|
|
72
|
-
return projects.map((entry) => ({ ...entry, id: uniqueProjectId(entry, seenIds) }));
|
|
73
|
-
}
|
|
74
|
-
function normalizeProjectEntry(raw, fallbackPath) {
|
|
75
|
-
if (!raw || typeof raw !== 'object')
|
|
76
|
-
return null;
|
|
77
|
-
const value = raw;
|
|
78
|
-
const rawPath = typeof value.folderPath === 'string'
|
|
79
|
-
? value.folderPath
|
|
80
|
-
: (typeof value.path === 'string'
|
|
81
|
-
? value.path
|
|
82
|
-
: (typeof value.folder === 'string' ? value.folder : fallbackPath));
|
|
83
|
-
if (!rawPath || typeof rawPath !== 'string')
|
|
84
|
-
return null;
|
|
85
|
-
const folderPath = normalizeProjectPath(rawPath);
|
|
86
|
-
const basename = path_1.default.basename(folderPath) || folderPath;
|
|
87
|
-
const rawId = typeof value.id === 'string' ? value.id.trim() : '';
|
|
88
|
-
// The employee roster is deliberately dropped here (not read, not persisted):
|
|
89
|
-
// it is derived from the bootstrap's hired personas at render time so it stays
|
|
90
|
-
// consistent across machines. Any `team` on incoming/legacy state is discarded.
|
|
91
|
-
return {
|
|
92
|
-
id: rawId || projectIdForPath(folderPath),
|
|
93
|
-
name: typeof value.name === 'string' && value.name.length > 0 ? value.name : basename,
|
|
94
|
-
folderPath,
|
|
95
|
-
intent: typeof value.intent === 'string' ? value.intent : '',
|
|
96
|
-
outcome: typeof value.outcome === 'string' ? value.outcome : '',
|
|
97
|
-
rules: typeof value.rules === 'string' ? value.rules : '',
|
|
98
|
-
brief: typeof value.brief === 'string' ? value.brief : (typeof value.intent === 'string' ? value.intent : ''),
|
|
99
|
-
updatedAt: typeof value.updatedAt === 'string' ? value.updatedAt : undefined,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
function normalizeAiHubProjectList(projects, currentProjectPath, options = {}) {
|
|
103
|
-
const byPath = new Map();
|
|
104
|
-
const currentKey = currentProjectPath ? normalizeProjectPath(currentProjectPath) : null;
|
|
105
|
-
const removedKeys = new Set(normalizeRemovedProjectPaths(options.removedProjectPaths, currentProjectPath));
|
|
106
|
-
const add = (entry) => {
|
|
107
|
-
if (!entry)
|
|
108
|
-
return;
|
|
109
|
-
const key = normalizeProjectPath(entry.folderPath);
|
|
110
|
-
if (removedKeys.has(canonicalProjectPath(key)))
|
|
111
|
-
return;
|
|
112
|
-
if (!options.includeMissing && key !== currentKey && !projectPathExists(key))
|
|
113
|
-
return;
|
|
114
|
-
const existing = byPath.get(key);
|
|
115
|
-
byPath.set(key, existing ? { ...existing, ...entry, folderPath: key } : { ...entry, folderPath: key });
|
|
116
|
-
};
|
|
117
|
-
if (currentProjectPath)
|
|
118
|
-
add(normalizeProjectEntry({ folderPath: currentProjectPath }, currentProjectPath));
|
|
119
|
-
for (const project of projects)
|
|
120
|
-
add(normalizeProjectEntry(project));
|
|
121
|
-
return withUniqueProjectIds(Array.from(byPath.values()));
|
|
122
|
-
}
|
|
123
|
-
class AiHubPreferencesStore {
|
|
124
|
-
constructor(stateFilePath = path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), 'ai-hub-state.json')) {
|
|
125
|
-
this.stateFilePath = stateFilePath;
|
|
126
|
-
}
|
|
127
|
-
load(projectPath) {
|
|
128
|
-
if (!fs_1.default.existsSync(this.stateFilePath)) {
|
|
129
|
-
return defaultPreferences(projectPath);
|
|
130
|
-
}
|
|
131
|
-
try {
|
|
132
|
-
const raw = JSON.parse(fs_1.default.readFileSync(this.stateFilePath, 'utf8'));
|
|
133
|
-
const removedProjectPaths = normalizeRemovedProjectPaths(raw.removedProjectPaths, projectPath);
|
|
134
|
-
return {
|
|
135
|
-
projectPath: raw.projectPath || projectPath,
|
|
136
|
-
employeeId: (raw.employeeId === 'claude' || raw.employeeId === 'codex' || raw.employeeId === 'gemini' || raw.employeeId === 'copilot') ? raw.employeeId : DEFAULT_EMPLOYEE,
|
|
137
|
-
categoryId: typeof raw.categoryId === 'string' && raw.categoryId.length > 0 ? raw.categoryId : DEFAULT_CATEGORY,
|
|
138
|
-
recentJobIds: Array.isArray(raw.recentJobIds) ? raw.recentJobIds.filter((value) => typeof value === 'string') : [],
|
|
139
|
-
recentJobInstructions: (typeof raw.recentJobInstructions === 'object' && raw.recentJobInstructions !== null && !Array.isArray(raw.recentJobInstructions))
|
|
140
|
-
? raw.recentJobInstructions
|
|
141
|
-
: {},
|
|
142
|
-
personaKey: typeof raw.personaKey === 'string' ? raw.personaKey : null,
|
|
143
|
-
// Issue #750: apiKey is no longer a persisted field here at all — any
|
|
144
|
-
// legacy apiKey left over from a pre-#750 ai-hub-state.json is silently
|
|
145
|
-
// dropped, never read back. Identity comes from ~/.fraim/config.json only.
|
|
146
|
-
projects: normalizeAiHubProjectList(Array.isArray(raw.projects) ? raw.projects : [], projectPath, { removedProjectPaths }),
|
|
147
|
-
removedProjectPaths,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
return defaultPreferences(projectPath);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
save(preferences) {
|
|
155
|
-
fs_1.default.mkdirSync(path_1.default.dirname(this.stateFilePath), { recursive: true });
|
|
156
|
-
fs_1.default.writeFileSync(this.stateFilePath, JSON.stringify(preferences, null, 2));
|
|
157
|
-
}
|
|
158
|
-
saveProjects(projectPath, projects, options = {}) {
|
|
159
|
-
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
160
|
-
const preferences = this.load(normalizedProjectPath);
|
|
161
|
-
const reviveKeys = new Set(normalizeRemovedProjectPaths(options.reviveRemovedPaths, normalizedProjectPath));
|
|
162
|
-
const removedProjectPaths = normalizeRemovedProjectPaths(preferences.removedProjectPaths, normalizedProjectPath)
|
|
163
|
-
.filter((removedPath) => !reviveKeys.has(removedPath));
|
|
164
|
-
const nextProjects = normalizeAiHubProjectList(projects, normalizedProjectPath, { removedProjectPaths });
|
|
165
|
-
this.save({ ...preferences, projectPath: normalizedProjectPath, projects: nextProjects, removedProjectPaths });
|
|
166
|
-
return nextProjects;
|
|
167
|
-
}
|
|
168
|
-
mergeProjects(projectPath, projects) {
|
|
169
|
-
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
170
|
-
const preferences = this.load(normalizedProjectPath);
|
|
171
|
-
const removedProjectPaths = normalizeRemovedProjectPaths(preferences.removedProjectPaths, normalizedProjectPath);
|
|
172
|
-
const nextProjects = normalizeAiHubProjectList([...(preferences.projects || []), ...projects], normalizedProjectPath, { removedProjectPaths });
|
|
173
|
-
this.save({ ...preferences, projectPath: normalizedProjectPath, projects: nextProjects, removedProjectPaths });
|
|
174
|
-
return nextProjects;
|
|
175
|
-
}
|
|
176
|
-
removeProject(projectPath, projects, removedProjectPath) {
|
|
177
|
-
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
178
|
-
const preferences = this.load(normalizedProjectPath);
|
|
179
|
-
const removedProjectPaths = normalizeRemovedProjectPaths([...(preferences.removedProjectPaths || []), removedProjectPath], normalizedProjectPath);
|
|
180
|
-
const nextProjects = normalizeAiHubProjectList(projects, normalizedProjectPath, { removedProjectPaths });
|
|
181
|
-
this.save({ ...preferences, projectPath: normalizedProjectPath, projects: nextProjects, removedProjectPaths });
|
|
182
|
-
return nextProjects;
|
|
183
|
-
}
|
|
184
|
-
remember(preferences, jobId, instructions) {
|
|
185
|
-
const recentJobIds = jobId
|
|
186
|
-
? [jobId, ...preferences.recentJobIds.filter((value) => value !== jobId)].slice(0, 8)
|
|
187
|
-
: preferences.recentJobIds.slice(0, 8);
|
|
188
|
-
const recentJobInstructions = { ...preferences.recentJobInstructions };
|
|
189
|
-
if (jobId && instructions) {
|
|
190
|
-
recentJobInstructions[jobId] = instructions.slice(0, 500);
|
|
191
|
-
}
|
|
192
|
-
const next = {
|
|
193
|
-
...preferences,
|
|
194
|
-
recentJobIds,
|
|
195
|
-
recentJobInstructions,
|
|
196
|
-
};
|
|
197
|
-
this.save(next);
|
|
198
|
-
return next;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
exports.AiHubPreferencesStore = AiHubPreferencesStore;
|
|
@@ -1,88 +0,0 @@
|
|
|
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.HttpHubRemoteGateway = void 0;
|
|
7
|
-
exports.resolveFraimRemoteUrl = resolveFraimRemoteUrl;
|
|
8
|
-
const axios_1 = __importDefault(require("axios"));
|
|
9
|
-
function resolveFraimRemoteUrl(explicit) {
|
|
10
|
-
return (explicit || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me').replace(/\/+$/, '');
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Default HTTP-backed gateway. Mirrors the ProviderClient axios pattern:
|
|
14
|
-
* base URL from FRAIM_REMOTE_URL, `x-api-key` auth header, 10s timeout.
|
|
15
|
-
*/
|
|
16
|
-
class HttpHubRemoteGateway {
|
|
17
|
-
constructor(serverUrl) {
|
|
18
|
-
this.baseURL = resolveFraimRemoteUrl(serverUrl);
|
|
19
|
-
}
|
|
20
|
-
client(apiKey) {
|
|
21
|
-
return axios_1.default.create({
|
|
22
|
-
baseURL: this.baseURL,
|
|
23
|
-
headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
|
|
24
|
-
timeout: 10000,
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
async getPersonaState(apiKey) {
|
|
28
|
-
if (!apiKey)
|
|
29
|
-
return null;
|
|
30
|
-
try {
|
|
31
|
-
const res = await this.client(apiKey).get('/api/personas/me');
|
|
32
|
-
return res.data;
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
// 401 (no/expired key) or 404 (feature disabled) => treat as "no state" so the Hub
|
|
36
|
-
// renders a clean locked/not-signed-in view instead of crashing.
|
|
37
|
-
const status = err?.response?.status;
|
|
38
|
-
if (status === 401 || status === 404)
|
|
39
|
-
return null;
|
|
40
|
-
console.warn('[ai-hub] getPersonaState failed:', err?.message || err);
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
async listManagerTeam(apiKey) {
|
|
45
|
-
if (!apiKey)
|
|
46
|
-
return [];
|
|
47
|
-
try {
|
|
48
|
-
const res = await this.client(apiKey).get('/api/ai-hub/manager-team');
|
|
49
|
-
const team = (res.data?.team ?? []);
|
|
50
|
-
return Array.isArray(team) ? team : [];
|
|
51
|
-
}
|
|
52
|
-
catch (err) {
|
|
53
|
-
const status = err?.response?.status;
|
|
54
|
-
if (status === 401 || status === 404)
|
|
55
|
-
return [];
|
|
56
|
-
console.warn('[ai-hub] listManagerTeam failed:', err?.message || err);
|
|
57
|
-
return [];
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
async assignManagerTeam(apiKey, personaKey) {
|
|
61
|
-
if (!apiKey)
|
|
62
|
-
return { status: 401, body: { error: 'authentication_required' } };
|
|
63
|
-
try {
|
|
64
|
-
const res = await this.client(apiKey).post('/api/ai-hub/manager-team/assign', { personaKey });
|
|
65
|
-
return { status: res.status, body: res.data };
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
if (err?.response)
|
|
69
|
-
return { status: err.response.status, body: err.response.data };
|
|
70
|
-
console.warn('[ai-hub] assignManagerTeam failed:', err?.message || err);
|
|
71
|
-
return { status: 500, body: { error: 'internal_error' } };
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
async removeManagerTeam(apiKey, personaKey) {
|
|
75
|
-
if (!apiKey)
|
|
76
|
-
return;
|
|
77
|
-
try {
|
|
78
|
-
await this.client(apiKey).delete(`/api/ai-hub/manager-team/assign/${encodeURIComponent(personaKey)}`);
|
|
79
|
-
}
|
|
80
|
-
catch (err) {
|
|
81
|
-
const status = err?.response?.status;
|
|
82
|
-
if (status === 401 || status === 404)
|
|
83
|
-
return;
|
|
84
|
-
console.warn('[ai-hub] removeManagerTeam failed:', err?.message || err);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
exports.HttpHubRemoteGateway = HttpHubRemoteGateway;
|