fraim 2.0.154 → 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.
Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/dist/src/ai-hub/cert-store.js +70 -0
  3. package/dist/src/ai-hub/desktop-main.js +225 -50
  4. package/dist/src/ai-hub/hosts.js +135 -8
  5. package/dist/src/ai-hub/manager-turns.js +38 -0
  6. package/dist/src/ai-hub/office-sideload.js +138 -0
  7. package/dist/src/ai-hub/openclaw-bridge.js +239 -0
  8. package/dist/src/ai-hub/server.js +479 -48
  9. package/dist/src/ai-hub/word-sideload.js +95 -0
  10. package/dist/src/cli/commands/add-ide.js +9 -0
  11. package/dist/src/cli/commands/init-project.js +46 -34
  12. package/dist/src/cli/commands/login.js +1 -2
  13. package/dist/src/cli/commands/setup.js +0 -2
  14. package/dist/src/cli/commands/sync.js +41 -11
  15. package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +66 -2
  16. package/dist/src/cli/doctor/checks/workflow-checks.js +1 -65
  17. package/dist/src/cli/mcp/fraim-mcp-latest-launcher.js +136 -0
  18. package/dist/src/cli/mcp/mcp-server-registry.js +14 -10
  19. package/dist/src/cli/setup/auto-mcp-setup.js +1 -1
  20. package/dist/src/cli/setup/ide-invocation-surfaces.js +2 -2
  21. package/dist/src/cli/utils/fraim-gitignore.js +11 -0
  22. package/dist/src/cli/utils/github-workflow-sync.js +231 -0
  23. package/dist/src/cli/utils/managed-agent-paths.js +1 -1
  24. package/dist/src/cli/utils/project-bootstrap.js +6 -3
  25. package/dist/src/cli/utils/remote-sync.js +1 -1
  26. package/dist/src/core/ai-mentor.js +46 -37
  27. package/dist/src/core/config-loader.js +69 -2
  28. package/dist/src/core/fraim-config-schema.generated.js +267 -6
  29. package/dist/src/core/types.js +0 -1
  30. package/dist/src/core/utils/fraim-labels.js +182 -0
  31. package/dist/src/core/utils/git-utils.js +22 -1
  32. package/dist/src/core/utils/project-fraim-paths.js +58 -0
  33. package/dist/src/first-run/session-service.js +3 -3
  34. package/dist/src/first-run/types.js +1 -1
  35. package/dist/src/local-mcp-server/learning-context-builder.js +77 -52
  36. package/dist/src/local-mcp-server/stdio-server.js +212 -13
  37. package/package.json +6 -2
  38. package/public/ai-hub/index.html +289 -229
  39. package/public/ai-hub/powerpoint-taskpane/icon-64.png +0 -0
  40. package/public/ai-hub/powerpoint-taskpane/index.html +235 -0
  41. package/public/ai-hub/powerpoint-taskpane/manifest.xml +30 -0
  42. package/public/ai-hub/script.js +1155 -586
  43. package/public/ai-hub/styles.css +1226 -722
  44. package/public/first-run/index.html +35 -35
  45. package/public/first-run/script.js +667 -667
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ /**
3
+ * Sideloads Office add-in manifests (Word + PowerPoint) so they appear under
4
+ * Insert > My Add-ins > Developer Add-ins without admin rights or AppSource.
5
+ *
6
+ * Windows: writes HKCU\SOFTWARE\Microsoft\Office\16.0\WEF\Developer\<guid> =
7
+ * ABSOLUTE FILE PATH to manifest.xml. This is exactly what Microsoft's
8
+ * `office-addin-dev-settings register` writes. A URL value is for
9
+ * SharePoint/network-share catalogs and yields "catalog access denied"
10
+ * for a developer sideload — do NOT use a URL here.
11
+ * macOS: copies the manifest into each app's
12
+ * ~/Library/Containers/<bundle>/Data/Documents/wef/<guid>.xml
13
+ *
14
+ * NOTE: the manifest XML itself must be ASCII-clean (no smart quotes, em/en
15
+ * dashes, or BOM) or Office's parser hard-fails with -1072894428 and the pane
16
+ * silently never appears. See project_rules.md.
17
+ *
18
+ * Both paths are non-admin and survive app updates (keyed by manifest GUID).
19
+ * Safe to call multiple times — idempotent.
20
+ */
21
+ var __importDefault = (this && this.__importDefault) || function (mod) {
22
+ return (mod && mod.__esModule) ? mod : { "default": mod };
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.isSideloaded = isSideloaded;
26
+ exports.sideloadManifest = sideloadManifest;
27
+ exports.removeSideload = removeSideload;
28
+ const fs_1 = __importDefault(require("fs"));
29
+ const path_1 = __importDefault(require("path"));
30
+ const os_1 = __importDefault(require("os"));
31
+ const child_process_1 = require("child_process");
32
+ const WEF_DEVELOPER_KEY = 'HKCU\\SOFTWARE\\Microsoft\\Office\\16.0\\WEF\\Developer';
33
+ const MANIFESTS = [
34
+ {
35
+ guid: 'd1090951-50cf-4cf2-9d12-b0f8541d265c',
36
+ candidates: (base) => [
37
+ path_1.default.resolve(base, 'extensions/office-word/manifest.xml'),
38
+ path_1.default.resolve(__dirname, '..', '..', 'extensions/office-word/manifest.xml'),
39
+ path_1.default.resolve(__dirname, '..', '..', '..', 'extensions/office-word/manifest.xml'),
40
+ ],
41
+ macContainer: 'com.microsoft.Word',
42
+ },
43
+ {
44
+ guid: 'e7a3c812-91fd-4b2e-8c15-d4f6a903b71e',
45
+ candidates: (base) => [
46
+ path_1.default.resolve(base, 'public/ai-hub/powerpoint-taskpane/manifest.xml'),
47
+ path_1.default.resolve(__dirname, '..', '..', 'public/ai-hub/powerpoint-taskpane/manifest.xml'),
48
+ path_1.default.resolve(__dirname, '..', '..', '..', 'public/ai-hub/powerpoint-taskpane/manifest.xml'),
49
+ ],
50
+ macContainer: 'com.microsoft.Powerpoint',
51
+ },
52
+ ];
53
+ function resolveManifestPath(entry, projectPath) {
54
+ return entry.candidates(projectPath).find(c => fs_1.default.existsSync(c)) ?? null;
55
+ }
56
+ function macWefPath(container, guid) {
57
+ return path_1.default.join(os_1.default.homedir(), 'Library', 'Containers', container, 'Data', 'Documents', 'wef', `${guid}.xml`);
58
+ }
59
+ // ---------------------------------------------------------------------------
60
+ // Windows registry helpers
61
+ // ---------------------------------------------------------------------------
62
+ function winRegisteredValue(guid) {
63
+ const r = (0, child_process_1.spawnSync)('reg', ['query', WEF_DEVELOPER_KEY, '/v', guid], { encoding: 'utf8' });
64
+ if (r.status !== 0 || !r.stdout.includes('REG_SZ'))
65
+ return null;
66
+ // Output line looks like: " <guid> REG_SZ C:\path\to\manifest.xml"
67
+ const line = r.stdout.split(/\r?\n/).find(l => l.includes('REG_SZ'));
68
+ if (!line)
69
+ return null;
70
+ const idx = line.indexOf('REG_SZ');
71
+ return line.slice(idx + 'REG_SZ'.length).trim() || null;
72
+ }
73
+ // ---------------------------------------------------------------------------
74
+ // Public API
75
+ // ---------------------------------------------------------------------------
76
+ /** Returns true when ALL manifests are sideloaded with the correct value. */
77
+ function isSideloaded() {
78
+ return MANIFESTS.every(m => isManifestSideloaded(m));
79
+ }
80
+ function isManifestSideloaded(m) {
81
+ if (process.platform === 'win32') {
82
+ const value = winRegisteredValue(m.guid);
83
+ // Must be a real file path that exists — a URL or a stale/missing path
84
+ // means we have NOT correctly sideloaded and should re-register.
85
+ return !!value && !/^https?:\/\//i.test(value) && fs_1.default.existsSync(value);
86
+ }
87
+ if (process.platform === 'darwin')
88
+ return fs_1.default.existsSync(macWefPath(m.macContainer, m.guid));
89
+ return false;
90
+ }
91
+ /**
92
+ * Sideloads all manifests. Returns ok:true if every manifest was registered.
93
+ * Returns ok:false with the first failure reason.
94
+ */
95
+ function sideloadManifest(projectPath) {
96
+ for (const entry of MANIFESTS) {
97
+ const manifestPath = resolveManifestPath(entry, projectPath);
98
+ if (!manifestPath) {
99
+ return { ok: false, reason: `Manifest not found for GUID ${entry.guid} — check extensions/office-word/ and public/ai-hub/powerpoint-taskpane/` };
100
+ }
101
+ if (process.platform === 'win32') {
102
+ // Developer-key value = absolute file path to manifest.xml (NOT a URL).
103
+ const r = (0, child_process_1.spawnSync)('reg', [
104
+ 'add', WEF_DEVELOPER_KEY,
105
+ '/v', entry.guid, '/t', 'REG_SZ', '/d', manifestPath, '/f',
106
+ ], { encoding: 'utf8' });
107
+ if (r.status !== 0)
108
+ return { ok: false, reason: r.stderr || `reg add failed for ${entry.guid}` };
109
+ continue;
110
+ }
111
+ if (process.platform === 'darwin') {
112
+ const dest = macWefPath(entry.macContainer, entry.guid);
113
+ try {
114
+ fs_1.default.mkdirSync(path_1.default.dirname(dest), { recursive: true });
115
+ fs_1.default.copyFileSync(manifestPath, dest);
116
+ }
117
+ catch (err) {
118
+ return { ok: false, reason: String(err) };
119
+ }
120
+ continue;
121
+ }
122
+ return { ok: false, reason: `Unsupported platform: ${process.platform}` };
123
+ }
124
+ return { ok: true };
125
+ }
126
+ /** Removes all sideloaded manifests. */
127
+ function removeSideload() {
128
+ for (const entry of MANIFESTS) {
129
+ if (process.platform === 'win32') {
130
+ (0, child_process_1.spawnSync)('reg', ['delete', WEF_DEVELOPER_KEY, '/v', entry.guid, '/f'], { encoding: 'utf8' });
131
+ }
132
+ else if (process.platform === 'darwin') {
133
+ const target = macWefPath(entry.macContainer, entry.guid);
134
+ if (fs_1.default.existsSync(target))
135
+ fs_1.default.unlinkSync(target);
136
+ }
137
+ }
138
+ }
@@ -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;