fraim-hub 2.0.333 → 2.0.337
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/dist/src/ai-hub/attachment-store.js +35 -2
- package/dist/src/ai-hub/desktop-main.js +5 -0
- package/dist/src/ai-hub/native-voice-input.js +141 -0
- package/dist/src/ai-hub/server.js +29 -2
- package/package.json +2 -2
- package/public/ai-hub/index.html +51 -65
- package/public/ai-hub/review.css +3 -2
- package/public/ai-hub/script.js +501 -682
- package/public/ai-hub/styles.css +93 -330
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -30,7 +63,6 @@ exports.safeAttachmentMetaPath = safeAttachmentMetaPath;
|
|
|
30
63
|
* path its own file-read tool opens.
|
|
31
64
|
*/
|
|
32
65
|
const path_1 = __importDefault(require("path"));
|
|
33
|
-
const sharp_1 = __importDefault(require("sharp"));
|
|
34
66
|
/** Accepted upload MIME types (spec R1/R11). */
|
|
35
67
|
exports.ACCEPTED_ATTACHMENT_MIME_TYPES = new Set([
|
|
36
68
|
'image/png',
|
|
@@ -90,8 +122,9 @@ function pdfContainsScriptMarker(buffer) {
|
|
|
90
122
|
async function sanitizeAttachmentBuffer(buffer, mimeType) {
|
|
91
123
|
if (IMAGE_MIME_TYPES.has(mimeType)) {
|
|
92
124
|
try {
|
|
125
|
+
const sharp = (await Promise.resolve().then(() => __importStar(require('sharp')))).default;
|
|
93
126
|
const format = mimeType === 'image/jpeg' ? 'jpeg' : mimeType === 'image/gif' ? 'gif' : 'png';
|
|
94
|
-
return await (
|
|
127
|
+
return await sharp(buffer).toFormat(format).toBuffer();
|
|
95
128
|
}
|
|
96
129
|
catch (err) {
|
|
97
130
|
throw new AttachmentRejectedError(415, `Not a valid ${mimeType} image.`);
|
|
@@ -11,6 +11,7 @@ const electron_1 = require("electron");
|
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
12
|
const fs_1 = __importDefault(require("fs"));
|
|
13
13
|
const server_1 = require("./server");
|
|
14
|
+
const native_voice_input_1 = require("./native-voice-input");
|
|
14
15
|
const cert_store_1 = require("./cert-store");
|
|
15
16
|
const office_sideload_1 = require("./office-sideload");
|
|
16
17
|
const office_setup_policy_1 = require("./office-setup-policy");
|
|
@@ -672,6 +673,10 @@ async function launchDesktopShell(options) {
|
|
|
672
673
|
return result.canceled || result.filePaths.length === 0 ? null : result.filePaths[0];
|
|
673
674
|
},
|
|
674
675
|
restartToLatest: restartToLatestNow,
|
|
676
|
+
voiceInput: (0, native_voice_input_1.createNativeVoiceInputController)({
|
|
677
|
+
getWindow: () => mainWindow,
|
|
678
|
+
sendActionToFirstResponder: (action) => electron_1.Menu.sendActionToFirstResponder(action),
|
|
679
|
+
}),
|
|
675
680
|
hubMainDiagnostics: {
|
|
676
681
|
snapshot: () => (0, hub_main_diagnostics_1.getHubMainDiagnosticsSnapshot)(buildDesktopDebugSnapshot(runtimeId)),
|
|
677
682
|
},
|
|
@@ -0,0 +1,141 @@
|
|
|
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.WINDOWS_VOICE_INPUT_PROGRAM = exports.NativeVoiceInputError = void 0;
|
|
7
|
+
exports.getVoiceInputCapability = getVoiceInputCapability;
|
|
8
|
+
exports.requestWindowsVoiceInput = requestWindowsVoiceInput;
|
|
9
|
+
exports.createNativeVoiceInputController = createNativeVoiceInputController;
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
class NativeVoiceInputError extends Error {
|
|
13
|
+
constructor(code) {
|
|
14
|
+
super(code);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.name = 'NativeVoiceInputError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.NativeVoiceInputError = NativeVoiceInputError;
|
|
20
|
+
function getVoiceInputCapability(platform = process.platform, native = false) {
|
|
21
|
+
if (platform === 'win32') {
|
|
22
|
+
return {
|
|
23
|
+
platform: 'windows', mode: native ? 'native' : 'manual', shortcutLabel: 'Win+H',
|
|
24
|
+
guidance: 'Press Win+H to start Windows Voice Typing. If it does not appear, check Windows Settings > Privacy & security > Speech and Microphone.',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (platform === 'darwin') {
|
|
28
|
+
return {
|
|
29
|
+
platform: 'macos', mode: native ? 'native' : 'manual', shortcutLabel: 'Dictation shortcut',
|
|
30
|
+
guidance: 'Use the microphone key or your configured Dictation shortcut. If Dictation does not appear, enable it in System Settings > Keyboard > Dictation.',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return { platform: 'other', mode: 'unsupported', guidance: 'Voice input is available through system dictation on Windows and macOS.' };
|
|
34
|
+
}
|
|
35
|
+
// Fixed program only: no renderer or request data enters the native command.
|
|
36
|
+
// The 32-byte union and individually paced events were validated in the #1844 spike.
|
|
37
|
+
exports.WINDOWS_VOICE_INPUT_PROGRAM = String.raw `$ErrorActionPreference = 'Stop'
|
|
38
|
+
$source = @'
|
|
39
|
+
using System;
|
|
40
|
+
using System.ComponentModel;
|
|
41
|
+
using System.Runtime.InteropServices;
|
|
42
|
+
public static class FraimVoiceTyping {
|
|
43
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
44
|
+
private struct INPUT { public uint type; public InputUnion data; }
|
|
45
|
+
[StructLayout(LayoutKind.Explicit, Size = 32)]
|
|
46
|
+
private struct InputUnion { [FieldOffset(0)] public KEYBDINPUT keyboard; }
|
|
47
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
48
|
+
private struct KEYBDINPUT {
|
|
49
|
+
public ushort virtualKey; public ushort scanCode; public uint flags;
|
|
50
|
+
public uint time; public UIntPtr extraInfo;
|
|
51
|
+
}
|
|
52
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
53
|
+
private static extern uint SendInput(uint count, INPUT[] inputs, int size);
|
|
54
|
+
private static bool Emit(ushort key, uint flags) {
|
|
55
|
+
INPUT input = new INPUT { type = 1, data = new InputUnion {
|
|
56
|
+
keyboard = new KEYBDINPUT { virtualKey = key, flags = flags }
|
|
57
|
+
}};
|
|
58
|
+
return SendInput(1, new INPUT[] { input }, Marshal.SizeOf(typeof(INPUT))) == 1;
|
|
59
|
+
}
|
|
60
|
+
private static void Send(ushort key, uint flags) {
|
|
61
|
+
if (!Emit(key, flags)) throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
62
|
+
}
|
|
63
|
+
public static int Invoke() {
|
|
64
|
+
bool winDown = false, hDown = false;
|
|
65
|
+
try {
|
|
66
|
+
Send(0x5B, 0); winDown = true;
|
|
67
|
+
System.Threading.Thread.Sleep(40);
|
|
68
|
+
Send(0x48, 0); hDown = true;
|
|
69
|
+
System.Threading.Thread.Sleep(40);
|
|
70
|
+
Send(0x48, 2); hDown = false;
|
|
71
|
+
System.Threading.Thread.Sleep(40);
|
|
72
|
+
Send(0x5B, 2); winDown = false;
|
|
73
|
+
return 4;
|
|
74
|
+
} finally {
|
|
75
|
+
// Release each key independently even after partial emission.
|
|
76
|
+
if (hDown) Emit(0x48, 2);
|
|
77
|
+
if (winDown) Emit(0x5B, 2);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
'@
|
|
82
|
+
Add-Type -TypeDefinition $source -Language CSharp
|
|
83
|
+
Write-Output ([FraimVoiceTyping]::Invoke())`;
|
|
84
|
+
function requestWindowsVoiceInput(execute = node_child_process_1.execFile) {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const fail = () => reject(new NativeVoiceInputError('launch_failed'));
|
|
87
|
+
try {
|
|
88
|
+
execute(node_path_1.default.win32.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'), ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(exports.WINDOWS_VOICE_INPUT_PROGRAM, 'utf16le').toString('base64')],
|
|
89
|
+
// execFile kills only its child on timeout and calls back after close.
|
|
90
|
+
{ windowsHide: true, timeout: 5000, maxBuffer: 4096, encoding: 'utf8' }, (error, stdout) => {
|
|
91
|
+
if (error || stdout.trim() !== '4')
|
|
92
|
+
fail();
|
|
93
|
+
else
|
|
94
|
+
resolve();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
fail();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function createNativeVoiceInputController(options) {
|
|
103
|
+
const capability = getVoiceInputCapability(options.platform ?? process.platform, true);
|
|
104
|
+
let inFlight = false;
|
|
105
|
+
return {
|
|
106
|
+
capability,
|
|
107
|
+
async start() {
|
|
108
|
+
if (capability.mode === 'unsupported')
|
|
109
|
+
throw new NativeVoiceInputError('unsupported_platform');
|
|
110
|
+
if (inFlight)
|
|
111
|
+
throw new NativeVoiceInputError('launch_failed');
|
|
112
|
+
inFlight = true;
|
|
113
|
+
try {
|
|
114
|
+
try {
|
|
115
|
+
const win = options.getWindow();
|
|
116
|
+
if (!win || win.isDestroyed() || win.webContents.isDestroyed())
|
|
117
|
+
throw new Error('unavailable');
|
|
118
|
+
win.show();
|
|
119
|
+
win.focus();
|
|
120
|
+
win.webContents.focus();
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
throw new NativeVoiceInputError('window_unavailable');
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
if (capability.platform === 'windows')
|
|
127
|
+
await (options.requestWindows ?? requestWindowsVoiceInput)();
|
|
128
|
+
else
|
|
129
|
+
options.sendActionToFirstResponder('startDictation:');
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
throw new NativeVoiceInputError('launch_failed');
|
|
133
|
+
}
|
|
134
|
+
return { status: 'requested', capability, message: `System dictation requested. ${capability.guidance}` };
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
inFlight = false;
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -52,6 +52,7 @@ const os_1 = __importDefault(require("os"));
|
|
|
52
52
|
const crypto_1 = require("crypto");
|
|
53
53
|
const child_process_1 = require("child_process");
|
|
54
54
|
const https_1 = __importDefault(require("https"));
|
|
55
|
+
const native_voice_input_1 = require("./native-voice-input");
|
|
55
56
|
const types_1 = require("../first-run/types");
|
|
56
57
|
const local_provider_registry_1 = require("../cli/providers/local-provider-registry");
|
|
57
58
|
const learning_context_builder_1 = require("../local-mcp-server/learning-context-builder");
|
|
@@ -2944,6 +2945,7 @@ class AiHubServer {
|
|
|
2944
2945
|
this.configuredAgentStore = options.configuredAgentStore || new configured_agents_1.AiHubConfiguredAgentStore();
|
|
2945
2946
|
this.wordTaskpaneDir = options.wordTaskpaneDir ?? resolveWordTaskpaneDir(this.projectPath);
|
|
2946
2947
|
this.folderPicker = options.folderPicker ?? pickProjectPath;
|
|
2948
|
+
this.voiceInput = options.voiceInput;
|
|
2947
2949
|
this.restartToLatest = options.restartToLatest ?? (async () => ({ restarting: false }));
|
|
2948
2950
|
this.httpsPort = options.httpsPort;
|
|
2949
2951
|
this.certBundle = options.certBundle;
|
|
@@ -3579,6 +3581,7 @@ class AiHubServer {
|
|
|
3579
3581
|
// #755: the running build version, so the account menu can show it and flag
|
|
3580
3582
|
// when a newer version is available (see GET /api/ai-hub/version for `latest`).
|
|
3581
3583
|
version: (0, version_utils_1.getFraimVersion)(),
|
|
3584
|
+
voiceInput: this.voiceInput?.capability ?? (0, native_voice_input_1.getVoiceInputCapability)(),
|
|
3582
3585
|
project,
|
|
3583
3586
|
// Issue #750: `apiKey` is no longer a persisted AiHubPreferences field —
|
|
3584
3587
|
// it is overlaid on the wire response only, freshly resolved from
|
|
@@ -3826,10 +3829,10 @@ class AiHubServer {
|
|
|
3826
3829
|
return false;
|
|
3827
3830
|
}
|
|
3828
3831
|
}
|
|
3829
|
-
requireTrustedHubOrigin(req, res) {
|
|
3832
|
+
requireTrustedHubOrigin(req, res, message = 'Configured agent management is only allowed from the local Hub origin.') {
|
|
3830
3833
|
if (this.isTrustedHubOrigin(req))
|
|
3831
3834
|
return true;
|
|
3832
|
-
res.status(403).json({ error:
|
|
3835
|
+
res.status(403).json({ error: message });
|
|
3833
3836
|
return false;
|
|
3834
3837
|
}
|
|
3835
3838
|
applyRunHostSessionSignal(run, sessionId, hostIdForCommand = run.hostId) {
|
|
@@ -6392,6 +6395,30 @@ class AiHubServer {
|
|
|
6392
6395
|
return res.status(500).json({ error: error instanceof Error ? error.message : 'Could not restart to the latest version.' });
|
|
6393
6396
|
}
|
|
6394
6397
|
});
|
|
6398
|
+
this.app.post('/api/ai-hub/voice-input/start', async (req, res) => {
|
|
6399
|
+
if (!this.requireTrustedHubOrigin(req, res, 'System dictation is only allowed from the local Hub origin.'))
|
|
6400
|
+
return;
|
|
6401
|
+
const capability = this.voiceInput?.capability ?? (0, native_voice_input_1.getVoiceInputCapability)();
|
|
6402
|
+
const startedAt = Date.now();
|
|
6403
|
+
try {
|
|
6404
|
+
if (capability.mode === 'unsupported')
|
|
6405
|
+
throw new native_voice_input_1.NativeVoiceInputError('unsupported_platform');
|
|
6406
|
+
if (!this.voiceInput || capability.mode === 'manual') {
|
|
6407
|
+
(0, hub_main_diagnostics_1.recordHubMainDiagnostic)('hub.voice_input.manual_fallback', { platform: capability.platform });
|
|
6408
|
+
return res.json({ status: 'manual', capability, message: capability.guidance });
|
|
6409
|
+
}
|
|
6410
|
+
const result = await this.voiceInput.start();
|
|
6411
|
+
(0, hub_main_diagnostics_1.recordHubMainDiagnostic)('hub.voice_input.requested', { platform: capability.platform, mode: capability.mode, durationMs: Date.now() - startedAt });
|
|
6412
|
+
return res.json(result);
|
|
6413
|
+
}
|
|
6414
|
+
catch (error) {
|
|
6415
|
+
const code = error instanceof native_voice_input_1.NativeVoiceInputError ? error.code : 'launch_failed';
|
|
6416
|
+
const status = code === 'unsupported_platform' ? 422 : code === 'window_unavailable' ? 409 : 500;
|
|
6417
|
+
const message = code === 'window_unavailable' ? 'Could not focus the Hub window.' : code === 'launch_failed' ? 'Could not request system dictation.' : '';
|
|
6418
|
+
(0, hub_main_diagnostics_1.recordHubMainDiagnostic)('hub.voice_input.failed', { platform: capability.platform, code, durationMs: Date.now() - startedAt });
|
|
6419
|
+
return res.status(status).json({ code, capability, error: `${message} ${capability.guidance}`.trim() });
|
|
6420
|
+
}
|
|
6421
|
+
});
|
|
6395
6422
|
this.app.post('/api/ai-hub/project-path/pick', async (_req, res) => {
|
|
6396
6423
|
try {
|
|
6397
6424
|
const projectPath = await this.folderPicker();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-hub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.337",
|
|
4
4
|
"description": "FRAIM Hub local companion package.",
|
|
5
5
|
"author": "Sid Mathur <sid.mathur@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/mathursrus/FRAIM#readme",
|
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
"electron-updater": "^6.8.9",
|
|
222
222
|
"express": "^5.2.1",
|
|
223
223
|
"extract-zip": "^2.0.1",
|
|
224
|
-
"fraim": "2.0.
|
|
224
|
+
"fraim": "2.0.337",
|
|
225
225
|
"mongodb": "^7.0.0",
|
|
226
226
|
"node-cron": "4.2.1",
|
|
227
227
|
"node-edge-tts": "^1.2.10",
|
package/public/ai-hub/index.html
CHANGED
|
@@ -93,14 +93,16 @@
|
|
|
93
93
|
|
|
94
94
|
<!-- Issue #512 / #521 R6: Company area — same shell as Projects (jobs rail + collapsible context/learnings) -->
|
|
95
95
|
<section class="hub-area" id="area-company" hidden>
|
|
96
|
-
<div class="area-shell">
|
|
97
|
-
<nav class="area-rail" id="company-rail"></nav>
|
|
96
|
+
<div class="area-shell scope-shell">
|
|
97
|
+
<nav class="tree-sidebar area-rail" id="company-rail" aria-label="Company navigation"></nav>
|
|
98
|
+
<button class="tree-collapse-btn" type="button" aria-label="Collapse navigation" title="Collapse navigation" aria-controls="company-rail" aria-expanded="true"></button>
|
|
99
|
+
<div class="tree-resizer" role="separator" aria-orientation="vertical" aria-label="Resize navigation" aria-controls="company-rail"></div>
|
|
98
100
|
<div class="area-main">
|
|
99
101
|
<!-- Info view: context/learnings accordions shown when no conv is active -->
|
|
100
102
|
<div class="area-info-view" id="company-info-view">
|
|
101
103
|
<div class="hub-area-page">
|
|
102
104
|
<div class="eyebrow">FRAIM Hub</div>
|
|
103
|
-
<
|
|
105
|
+
<h1 class="area-h1">Company</h1>
|
|
104
106
|
<p class="area-lede">Your organization's identity, what you do, how you operate, and the guardrails every employee follows on every job.</p>
|
|
105
107
|
<div id="company-push-banner"></div>
|
|
106
108
|
<!-- #744: Brand editor — company identity across the Hub. -->
|
|
@@ -152,14 +154,16 @@
|
|
|
152
154
|
|
|
153
155
|
<!-- Issue #512 / #521 R6: Manager area — same shell as Projects -->
|
|
154
156
|
<section class="hub-area" id="area-manager" hidden>
|
|
155
|
-
<div class="area-shell">
|
|
156
|
-
<nav class="area-rail" id="manager-rail"></nav>
|
|
157
|
+
<div class="area-shell scope-shell">
|
|
158
|
+
<nav class="tree-sidebar area-rail" id="manager-rail" aria-label="Manager navigation"></nav>
|
|
159
|
+
<button class="tree-collapse-btn" type="button" aria-label="Collapse navigation" title="Collapse navigation" aria-controls="manager-rail" aria-expanded="true"></button>
|
|
160
|
+
<div class="tree-resizer" role="separator" aria-orientation="vertical" aria-label="Resize navigation" aria-controls="manager-rail"></div>
|
|
157
161
|
<div class="area-main">
|
|
158
162
|
<!-- Info view: context/learnings accordions shown when no conv is active -->
|
|
159
163
|
<div class="area-info-view" id="manager-info-view">
|
|
160
164
|
<div class="hub-area-page">
|
|
161
165
|
<div class="eyebrow">FRAIM Hub</div>
|
|
162
|
-
<
|
|
166
|
+
<h1 class="area-h1">Manager</h1>
|
|
163
167
|
<p class="area-lede">How you manage: your working style, preferences, and the standing rules your team follows when working with you.</p>
|
|
164
168
|
<details class="ctx-acc" id="manager-agents-acc" open>
|
|
165
169
|
<summary><span class="ca-chev">▸</span> <span>AI Agents</span></summary>
|
|
@@ -263,18 +267,19 @@
|
|
|
263
267
|
</div>
|
|
264
268
|
|
|
265
269
|
<!-- Project workspace: employee tree + the existing conversation surface. -->
|
|
266
|
-
<div id="proj-workspace" hidden>
|
|
267
|
-
<nav class="tree-sidebar" id="tree-sidebar"></nav>
|
|
268
|
-
<button class="tree-collapse-btn" id="tree-collapse-btn" type="button" aria-label="Collapse
|
|
269
|
-
<div class="tree-resizer" id="tree-resizer" role="separator" aria-orientation="vertical" aria-label="Resize
|
|
270
|
+
<div id="proj-workspace" class="scope-shell" hidden>
|
|
271
|
+
<nav class="tree-sidebar" id="tree-sidebar" aria-label="Project navigation"></nav>
|
|
272
|
+
<button class="tree-collapse-btn" id="tree-collapse-btn" type="button" aria-label="Collapse navigation" title="Collapse navigation" aria-controls="tree-sidebar" aria-expanded="true"></button>
|
|
273
|
+
<div class="tree-resizer" id="tree-resizer" role="separator" aria-orientation="vertical" aria-label="Resize navigation" aria-controls="tree-sidebar"></div>
|
|
270
274
|
<div class="workspace-conv">
|
|
271
275
|
|
|
272
|
-
<!--
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
276
|
+
<!-- Project Info uses the same titled page shell as Company and Manager.
|
|
277
|
+
The shared view state makes Info, conversation, and search exclusive. -->
|
|
278
|
+
<div id="proj-context-top" class="area-info-view" hidden>
|
|
279
|
+
<div class="hub-area-page">
|
|
280
|
+
<div class="eyebrow">FRAIM Hub</div>
|
|
281
|
+
<h1 class="area-h1" id="project-info-heading">Project</h1>
|
|
282
|
+
<p class="area-lede">This project's context, rules, learnings, and assignments.</p>
|
|
278
283
|
<!-- Issue #1198 / #1253 R4 / #1661: the folder this project points at,
|
|
279
284
|
always on screen. Relocation is an explicit Project Info action. -->
|
|
280
285
|
<div id="proj-folder-row" class="proj-folder-row" hidden>
|
|
@@ -320,6 +325,7 @@
|
|
|
320
325
|
</div>
|
|
321
326
|
</div>
|
|
322
327
|
</details>
|
|
328
|
+
</div>
|
|
323
329
|
</div>
|
|
324
330
|
|
|
325
331
|
|
|
@@ -437,12 +443,12 @@
|
|
|
437
443
|
</div>
|
|
438
444
|
|
|
439
445
|
<div class="manage-panel-shell">
|
|
440
|
-
<
|
|
441
|
-
<
|
|
446
|
+
<section class="panel-details panel-details--thread" id="thread-panel">
|
|
447
|
+
<div class="thread-header">
|
|
442
448
|
<span class="panel-summary-copy">
|
|
443
449
|
<span class="panel-kicker" id="thread-panel-kicker">Manage</span>
|
|
444
450
|
</span>
|
|
445
|
-
</
|
|
451
|
+
</div>
|
|
446
452
|
<!-- Issue #1065: find a passage inside the open conversation (R44-R46). A result that
|
|
447
453
|
promises "+6 more matches" is an unkept promise without this: the manager lands in
|
|
448
454
|
a thread that can run to hundreds of messages with no way to reach the match. -->
|
|
@@ -473,26 +479,13 @@
|
|
|
473
479
|
</div>
|
|
474
480
|
</div>
|
|
475
481
|
</div>
|
|
476
|
-
</
|
|
482
|
+
</section>
|
|
477
483
|
<div class="manage-header-actions">
|
|
478
|
-
<button class="conversation-focus-btn" id="conversation-focus-btn" type="button" aria-label="Expand conversation" title="Expand conversation">↗</button>
|
|
479
484
|
<button class="micro-manage-toggle" id="micro-manage-toggle" type="button" role="switch" aria-checked="false" aria-controls="manage-body"><span>Micro-Manage</span><span class="micro-switch-track" aria-hidden="true"></span></button>
|
|
480
485
|
</div>
|
|
481
486
|
</div>
|
|
482
487
|
|
|
483
488
|
<div class="support-stack">
|
|
484
|
-
<!-- Issue #770: single-run deliverables live here (a stable collapsible
|
|
485
|
-
panel), not pinned to the bottom of the thread. Static-and-hidden,
|
|
486
|
-
populated + toggled by renderDeliverablesPanel. -->
|
|
487
|
-
<details class="panel-details panel-details--deliverables" id="deliverables-panel" hidden>
|
|
488
|
-
<summary>
|
|
489
|
-
<span class="panel-summary-copy">
|
|
490
|
-
<span class="panel-kicker">Deliverables</span>
|
|
491
|
-
<span class="panel-summary-text" id="deliverables-summary"></span>
|
|
492
|
-
</span>
|
|
493
|
-
</summary>
|
|
494
|
-
<div class="panel-body" id="deliverables-body"></div>
|
|
495
|
-
</details>
|
|
496
489
|
<!-- Issue #1292: when this conversation's own agent stops being available,
|
|
497
490
|
say so. The selector keeps holding that agent; it is never quietly
|
|
498
491
|
swapped for another one. Lives outside the composer controls so
|
|
@@ -519,23 +512,23 @@
|
|
|
519
512
|
</div>
|
|
520
513
|
</div>
|
|
521
514
|
|
|
522
|
-
<
|
|
523
|
-
<
|
|
515
|
+
<section class="panel-details panel-details--thread" id="ab-direct-thread-panel">
|
|
516
|
+
<div class="thread-header">
|
|
524
517
|
<span class="panel-summary-copy">
|
|
525
518
|
<span class="panel-kicker">Direct output (raw)</span>
|
|
526
519
|
</span>
|
|
527
|
-
</
|
|
520
|
+
</div>
|
|
528
521
|
<pre class="ab-direct-log" id="ab-direct-log"></pre>
|
|
529
|
-
</
|
|
522
|
+
</section>
|
|
530
523
|
|
|
531
524
|
<div class="support-stack">
|
|
532
|
-
<
|
|
533
|
-
<
|
|
525
|
+
<section class="panel-details panel-details--coach" id="ab-direct-reply-panel">
|
|
526
|
+
<div class="thread-header">
|
|
534
527
|
<span class="panel-summary-copy">
|
|
535
528
|
<span class="panel-kicker">Reply to Direct agent</span>
|
|
536
|
-
<span class="panel-summary-text" id="ab-direct-reply-summary">
|
|
529
|
+
<span class="panel-summary-text" id="ab-direct-reply-summary">Send a follow-up</span>
|
|
537
530
|
</span>
|
|
538
|
-
</
|
|
531
|
+
</div>
|
|
539
532
|
<div class="panel-body">
|
|
540
533
|
<div class="coach">
|
|
541
534
|
<div class="coach-input">
|
|
@@ -544,7 +537,7 @@
|
|
|
544
537
|
</div>
|
|
545
538
|
</div>
|
|
546
539
|
</div>
|
|
547
|
-
</
|
|
540
|
+
</section>
|
|
548
541
|
</div>
|
|
549
542
|
</div>
|
|
550
543
|
|
|
@@ -555,9 +548,7 @@
|
|
|
555
548
|
visible even when no conversation is active"). Only the content
|
|
556
549
|
ABOVE this div changes (idle placeholder, hire strip, thread, or
|
|
557
550
|
the A/B compare panel); the composer itself never opens, closes,
|
|
558
|
-
or relocates
|
|
559
|
-
"expand conversation", handled by openConversationFocus/
|
|
560
|
-
closeConversationFocus alongside #thread-panel. -->
|
|
551
|
+
or relocates. -->
|
|
561
552
|
<div id="persistent-composer">
|
|
562
553
|
<div class="composer-suggestions" id="composer-suggestions" hidden>
|
|
563
554
|
<div id="cp-results" class="cp-results-scroll">
|
|
@@ -589,16 +580,16 @@
|
|
|
589
580
|
<button type="button" id="cp-armed-job-clear" aria-label="Change job">×</button>
|
|
590
581
|
</div>
|
|
591
582
|
<div class="staged-attachments" id="staged-attachments"></div>
|
|
592
|
-
|
|
593
|
-
<button class="rb-approve" type="button" id="review-approve">✓ Approve</button>
|
|
594
|
-
<button class="rb-secondary" type="button" id="review-request-changes">↩ Request changes</button>
|
|
595
|
-
<span class="rb-handoff" id="review-handoff">Comment on the doc to leave inline notes, or type below to coach.</span>
|
|
596
|
-
</div>
|
|
583
|
+
|
|
597
584
|
<div class="coach-input">
|
|
598
585
|
<input type="file" id="attach-file-input" multiple accept="image/png,image/jpeg,image/gif,.pdf,.txt,.log" hidden>
|
|
599
|
-
<
|
|
586
|
+
<div class="voice-input-status" id="voice-input-status" role="status" aria-live="polite" aria-atomic="true" hidden></div>
|
|
587
|
+
<textarea id="coach-text" aria-label="Type an instruction" placeholder="Type a job name, /handle, or describe what you need..." data-placeholder-idle="Type a job name, /handle, or describe what you need..." data-placeholder-review="Reply to continue the run, or use Approve / Request changes above..." data-placeholder-active="Coach this run, or type new instructions..." data-placeholder-armed="Now type what you want done..."></textarea>
|
|
600
588
|
<div class="composer-footer">
|
|
601
589
|
<button class="attach-btn" type="button" id="attach-btn" aria-label="Attach a file or screenshot" title="Attach a file or screenshot">📎</button>
|
|
590
|
+
<button class="composer-icon-btn voice-input-btn" type="button" id="voice-input-btn" disabled aria-label="Start voice input" aria-describedby="voice-input-status" title="Loading voice input availability">
|
|
591
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M5 10v2a7 7 0 0 0 14 0v-2M12 19v3M8 22h8"/></svg>
|
|
592
|
+
</button>
|
|
602
593
|
<div class="quick-coach-row composer-coach-actions" id="quick-coach-btns" hidden>
|
|
603
594
|
<button class="ghost quick-coach-btn mark-complete-btn composer-icon-btn" type="button" id="mark-complete-btn" aria-label="Mark the job complete" title="Mark complete">✓</button>
|
|
604
595
|
<button class="run-stop-btn composer-stop-btn" id="run-stop-btn" type="button" hidden aria-label="Stop the employee" title="Stop the employee">⏹</button>
|
|
@@ -683,7 +674,7 @@
|
|
|
683
674
|
</main>
|
|
684
675
|
</div>
|
|
685
676
|
|
|
686
|
-
|
|
677
|
+
|
|
687
678
|
</div>
|
|
688
679
|
|
|
689
680
|
<!-- Issue #1065: search results replace the conversation panel while a query is active,
|
|
@@ -1130,19 +1121,6 @@
|
|
|
1130
1121
|
</div>
|
|
1131
1122
|
</div>
|
|
1132
1123
|
|
|
1133
|
-
<!-- Issue #539: Command palette overlay — replaces 2-step modal as job-launch surface -->
|
|
1134
|
-
<section class="conversation-focus-overlay" id="conversation-focus-overlay" role="dialog" aria-modal="true" aria-labelledby="conversation-focus-title" hidden>
|
|
1135
|
-
<div class="conversation-focus-dialog">
|
|
1136
|
-
<div class="conversation-focus-header">
|
|
1137
|
-
<div class="conversation-focus-copy">
|
|
1138
|
-
<strong id="conversation-focus-title">Focused conversation</strong>
|
|
1139
|
-
<span id="conversation-focus-subtitle">Conversation thread and coaching</span>
|
|
1140
|
-
</div>
|
|
1141
|
-
<button class="conversation-focus-close" id="conversation-focus-close" type="button" aria-label="Close focused conversation">Close</button>
|
|
1142
|
-
</div>
|
|
1143
|
-
<div class="conversation-focus-panels" id="conversation-focus-panels"></div>
|
|
1144
|
-
</div>
|
|
1145
|
-
</section>
|
|
1146
1124
|
|
|
1147
1125
|
<!-- Issue #1657 Treatment 3: #cp-modal is retired as a separate dialog.
|
|
1148
1126
|
The ids below (cp-recent-list, cp-catalog-list, cp-agent-picker, etc.)
|
|
@@ -1265,7 +1243,15 @@
|
|
|
1265
1243
|
</div>
|
|
1266
1244
|
</div>
|
|
1267
1245
|
|
|
1246
|
+
<div id="review-action-storage" hidden>
|
|
1247
|
+
<div class="review-actions" id="review-actions" hidden>
|
|
1248
|
+
<button class="rb-approve" type="button" id="review-approve">✓ Approve</button>
|
|
1249
|
+
<button class="rb-secondary" type="button" id="review-request-changes">↩ Request changes</button>
|
|
1250
|
+
<span class="rb-handoff" id="review-handoff">Comment on the doc to leave inline notes, or type below to coach.</span>
|
|
1251
|
+
</div>
|
|
1252
|
+
</div>
|
|
1268
1253
|
<script src="./script.js?v=dark-status-20260721"></script>
|
|
1269
1254
|
<script src="./remote-ui-loader.js?v=server-driven-ui-20260724"></script>
|
|
1255
|
+
|
|
1270
1256
|
</body>
|
|
1271
1257
|
</html>
|
package/public/ai-hub/review.css
CHANGED
|
@@ -422,8 +422,6 @@
|
|
|
422
422
|
.rev-item .rev-dismiss:hover { background: var(--soft); }
|
|
423
423
|
|
|
424
424
|
/* ── #770: Deliverables panel body spacing for the relocated artifact strip. ── */
|
|
425
|
-
#deliverables-panel .panel-body { padding-top: 8px; }
|
|
426
|
-
#deliverables-panel .rc-artifact-strip { margin: 0; }
|
|
427
425
|
|
|
428
426
|
/* ── R9: project-scoped learnings panel (over the workspace conversation) ── */
|
|
429
427
|
.proj-learnings-panel {
|
|
@@ -633,3 +631,6 @@
|
|
|
633
631
|
flex-direction: column;
|
|
634
632
|
gap: 6px;
|
|
635
633
|
}
|
|
634
|
+
|
|
635
|
+
.review-turn .review-actions { border: 0; margin: 0; padding: 0; }
|
|
636
|
+
.review-turn .rb-handoff { flex-basis: 100%; }
|