@leg3ndy/otto-bridge 0.8.5 → 0.9.1
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 +17 -6
- package/dist/executors/native_macos.js +147 -22
- package/dist/runtime.js +14 -0
- package/dist/tool_catalog.js +193 -0
- package/dist/types.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,10 @@ Para um passo a passo de instalacao, pareamento, uso, desconexao e desinstalacao
|
|
|
13
13
|
|
|
14
14
|
Para o estado atual da arquitetura, capacidades entregues, limitacoes e roadmap do Otto Bridge, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_ARCHITECTURE.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_ARCHITECTURE.md).
|
|
15
15
|
|
|
16
|
+
Para o corte de arquitetura do `0.9.0`, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_0_9_0_RELEASE.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_0_9_0_RELEASE.md).
|
|
17
|
+
|
|
18
|
+
Para o patch atual `0.9.1`, com hotfixes de stream/renderizacao e completude das filesystem tools, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_0_9_1_PATCH.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_0_9_1_PATCH.md).
|
|
19
|
+
|
|
16
20
|
## Distribuicao
|
|
17
21
|
|
|
18
22
|
Fluxo recomendado agora:
|
|
@@ -33,12 +37,14 @@ Enquanto o pacote nao estiver publicado, voce pode gerar um tarball local:
|
|
|
33
37
|
|
|
34
38
|
```bash
|
|
35
39
|
npm pack
|
|
36
|
-
npm install -g ./leg3ndy-otto-bridge-0.
|
|
40
|
+
npm install -g ./leg3ndy-otto-bridge-0.9.1.tgz
|
|
37
41
|
```
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
Na linha `0.9.1`, `playwright` segue como dependencia obrigatoria no `otto-bridge`. O primeiro `npm install -g @leg3ndy/otto-bridge` pode demorar mais porque instala o browser persistente usado pelo WhatsApp Web e pelos fluxos web em background do bridge.
|
|
44
|
+
|
|
45
|
+
No macOS, a linha `0.9.1` usa o provider `macos-helper`, um helper `WKWebView` sem Dock para o WhatsApp Web. O helper sobe com user-agent de Chrome moderno para evitar o bloqueio do WhatsApp ao detectar Safari/WebKit. O runtime antigo com Chromium/Playwright fica disponivel apenas como override explicito via `OTTO_BRIDGE_WHATSAPP_RUNTIME_PROVIDER=embedded-playwright`.
|
|
40
46
|
|
|
41
|
-
No
|
|
47
|
+
No nivel arquitetural, o `0.9.0` marcou a mudanca de papel do bridge: ele publica tools e resultados estruturados para o Otto, em vez de injetar resposta pronta como caminho principal do chat. O `0.9.1` consolida esse caminho com hotfixes de stream visivel, planner LLM-first e leitura/listagem local sem truncamento silencioso.
|
|
42
48
|
|
|
43
49
|
## Publicacao
|
|
44
50
|
|
|
@@ -108,7 +114,7 @@ otto-bridge run --executor clawd-cursor --clawd-url http://127.0.0.1:3847
|
|
|
108
114
|
|
|
109
115
|
### WhatsApp Web em background
|
|
110
116
|
|
|
111
|
-
Fluxo recomendado
|
|
117
|
+
Fluxo recomendado na linha `0.9.1`:
|
|
112
118
|
|
|
113
119
|
```bash
|
|
114
120
|
otto-bridge extensions --install whatsappweb
|
|
@@ -118,13 +124,13 @@ otto-bridge extensions --status whatsappweb
|
|
|
118
124
|
|
|
119
125
|
O setup agora abre o login do WhatsApp Web no helper/background browser do proprio bridge. Depois do QR code, o Otto usa a sessao local em background, sem depender de aba visivel no Safari.
|
|
120
126
|
|
|
121
|
-
Contrato
|
|
127
|
+
Contrato da linha `0.9.1`:
|
|
122
128
|
|
|
123
129
|
- `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
|
|
124
130
|
- `otto-bridge run`: mantem o browser persistente do WhatsApp vivo em background enquanto o runtime estiver ativo, sem depender de uma aba aberta no Safari
|
|
125
131
|
- ao parar o `otto-bridge run`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
|
|
126
132
|
|
|
127
|
-
## Handoff rapido
|
|
133
|
+
## Handoff rapido da linha 0.9.1
|
|
128
134
|
|
|
129
135
|
Ja fechado no codigo:
|
|
130
136
|
|
|
@@ -132,6 +138,11 @@ Ja fechado no codigo:
|
|
|
132
138
|
- user-agent do helper ajustado para evitar bloqueio do WhatsApp por detecao de Safari/WebKit
|
|
133
139
|
- resultado final dos `device_job` agora e persistido como contexto mais forte para o proximo turno do Otto
|
|
134
140
|
- prompt bridge-aware no chat normal para ajudar o Otto a responder com base no que realmente aconteceu no device
|
|
141
|
+
- runtime local agora publica `local_tools` para o Otto/backend saberem exatamente o que o device consegue fazer
|
|
142
|
+
- o caminho principal do bridge usa `summary`/`narration_context` no lugar de resposta automatica pronta
|
|
143
|
+
- `read_file` agora entrega conteudo completo segmentado em `content_chunks` para o Otto
|
|
144
|
+
- `list_files` sem `limit` agora lista o diretorio inteiro, sem fallback silencioso para 40 itens
|
|
145
|
+
- o planner do bridge prioriza o modelo para escolher a tool certa pelo contexto; regex fica como fallback
|
|
135
146
|
|
|
136
147
|
Ainda precisa reteste em campo:
|
|
137
148
|
|
|
@@ -702,6 +702,35 @@ function clipTextPreview(value, maxLength) {
|
|
|
702
702
|
}
|
|
703
703
|
return `${value.slice(0, maxLength)}\n\n[conteudo truncado: mostrando ${maxLength} de ${value.length} caracteres. Peca um trecho mais especifico se quiser continuar.]`;
|
|
704
704
|
}
|
|
705
|
+
function chunkTextForTransport(value, chunkSize) {
|
|
706
|
+
const normalized = String(value || "");
|
|
707
|
+
if (!normalized) {
|
|
708
|
+
return [];
|
|
709
|
+
}
|
|
710
|
+
const safeChunkSize = Math.max(400, Math.min(Math.round(chunkSize || 4000), 32_000));
|
|
711
|
+
const chunks = [];
|
|
712
|
+
let cursor = 0;
|
|
713
|
+
let index = 0;
|
|
714
|
+
while (cursor < normalized.length) {
|
|
715
|
+
let nextCursor = Math.min(normalized.length, cursor + safeChunkSize);
|
|
716
|
+
if (nextCursor < normalized.length) {
|
|
717
|
+
const lastLineBreak = normalized.lastIndexOf("\n", nextCursor);
|
|
718
|
+
if (lastLineBreak > cursor + Math.floor(safeChunkSize * 0.35)) {
|
|
719
|
+
nextCursor = lastLineBreak + 1;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
const text = normalized.slice(cursor, nextCursor);
|
|
723
|
+
chunks.push({
|
|
724
|
+
index,
|
|
725
|
+
start_char: cursor,
|
|
726
|
+
end_char: nextCursor,
|
|
727
|
+
text,
|
|
728
|
+
});
|
|
729
|
+
cursor = nextCursor;
|
|
730
|
+
index += 1;
|
|
731
|
+
}
|
|
732
|
+
return chunks;
|
|
733
|
+
}
|
|
705
734
|
const TEXTUTIL_READABLE_EXTENSIONS = new Set([
|
|
706
735
|
".doc",
|
|
707
736
|
".docx",
|
|
@@ -975,14 +1004,14 @@ function parseStructuredActions(job) {
|
|
|
975
1004
|
if (type === "read_file" || type === "read_local_file") {
|
|
976
1005
|
const filePath = asString(action.path);
|
|
977
1006
|
if (filePath) {
|
|
978
|
-
const maxChars = typeof action.max_chars === "number" ? Math.max(
|
|
1007
|
+
const maxChars = typeof action.max_chars === "number" ? Math.max(400, Math.min(32_000, action.max_chars)) : undefined;
|
|
979
1008
|
actions.push({ type: "read_file", path: filePath, max_chars: maxChars });
|
|
980
1009
|
}
|
|
981
1010
|
continue;
|
|
982
1011
|
}
|
|
983
1012
|
if (type === "list_files" || type === "ls") {
|
|
984
1013
|
const filePath = asString(action.path) || "~";
|
|
985
|
-
const limit = typeof action.limit === "number" ? Math.max(1, Math.min(
|
|
1014
|
+
const limit = typeof action.limit === "number" ? Math.max(1, Math.min(5_000, action.limit)) : undefined;
|
|
986
1015
|
actions.push({ type: "list_files", path: filePath, limit });
|
|
987
1016
|
continue;
|
|
988
1017
|
}
|
|
@@ -1389,14 +1418,18 @@ export class NativeMacOSJobExecutor {
|
|
|
1389
1418
|
}
|
|
1390
1419
|
if (action.type === "read_file") {
|
|
1391
1420
|
await reporter.progress(progressPercent, `Lendo ${action.path}`);
|
|
1392
|
-
const fileContent = await this.
|
|
1393
|
-
|
|
1421
|
+
const fileContent = await this.readLocalFileSnapshot(action.path, action.max_chars);
|
|
1422
|
+
resultPayload.read_file = fileContent;
|
|
1423
|
+
resultPayload.summary = fileContent.summary;
|
|
1424
|
+
completionNotes.push(fileContent.summary);
|
|
1394
1425
|
continue;
|
|
1395
1426
|
}
|
|
1396
1427
|
if (action.type === "list_files") {
|
|
1397
1428
|
await reporter.progress(progressPercent, `Listando arquivos em ${action.path}`);
|
|
1398
|
-
const listing = await this.
|
|
1399
|
-
|
|
1429
|
+
const listing = await this.listLocalFilesSnapshot(action.path, action.limit);
|
|
1430
|
+
resultPayload.file_listing = listing;
|
|
1431
|
+
resultPayload.summary = listing.summary;
|
|
1432
|
+
completionNotes.push(listing.summary);
|
|
1400
1433
|
continue;
|
|
1401
1434
|
}
|
|
1402
1435
|
if (action.type === "count_files") {
|
|
@@ -2364,7 +2397,7 @@ return appNames as text
|
|
|
2364
2397
|
snapshot.summary = `A pasta ${targetPath} tem ${entries.length} item${entries.length === 1 ? "" : "s"} e ocupa ${formatBytesCompact(totalSize)}.${childPreview ? ` Itens visiveis agora: ${childPreview}.` : ""}`;
|
|
2365
2398
|
return snapshot;
|
|
2366
2399
|
}
|
|
2367
|
-
const preview = includePreview ? await this.
|
|
2400
|
+
const preview = includePreview ? await this.readLocalFilePreview(resolved, 1200) : undefined;
|
|
2368
2401
|
const snapshot = {
|
|
2369
2402
|
captured_at: new Date().toISOString(),
|
|
2370
2403
|
path: targetPath,
|
|
@@ -4961,9 +4994,9 @@ if let output = String(data: data, encoding: .utf8) {
|
|
|
4961
4994
|
resized,
|
|
4962
4995
|
};
|
|
4963
4996
|
}
|
|
4964
|
-
async
|
|
4965
|
-
const resolved = await this.resolveReadableFilePath(filePath);
|
|
4997
|
+
async loadReadableFileContent(resolved) {
|
|
4966
4998
|
const extension = path.extname(resolved).toLowerCase();
|
|
4999
|
+
const mimeType = mimeTypeFromPath(resolved);
|
|
4967
5000
|
if (TEXTUTIL_READABLE_EXTENSIONS.has(extension)) {
|
|
4968
5001
|
const { stdout } = await this.runCommandCapture("textutil", [
|
|
4969
5002
|
"-convert",
|
|
@@ -4971,17 +5004,76 @@ if let output = String(data: data, encoding: .utf8) {
|
|
|
4971
5004
|
"-stdout",
|
|
4972
5005
|
resolved,
|
|
4973
5006
|
]);
|
|
4974
|
-
|
|
4975
|
-
|
|
5007
|
+
return {
|
|
5008
|
+
mimeType,
|
|
5009
|
+
encoding: "utf-8",
|
|
5010
|
+
isBinary: false,
|
|
5011
|
+
content: sanitizeTextForJsonTransport(stdout) || "(arquivo sem texto legivel)",
|
|
5012
|
+
};
|
|
4976
5013
|
}
|
|
4977
5014
|
const raw = await readFile(resolved);
|
|
4978
5015
|
if (isLikelyBinaryBuffer(raw)) {
|
|
4979
5016
|
const filename = path.basename(resolved);
|
|
4980
5017
|
const detectedType = extension || "binario";
|
|
4981
|
-
return
|
|
5018
|
+
return {
|
|
5019
|
+
mimeType,
|
|
5020
|
+
isBinary: true,
|
|
5021
|
+
binaryNotice: `O arquivo ${filename} parece ser binario (${detectedType}) e nao pode ser lido como texto puro pelo Otto Bridge ainda.`,
|
|
5022
|
+
};
|
|
5023
|
+
}
|
|
5024
|
+
return {
|
|
5025
|
+
mimeType,
|
|
5026
|
+
encoding: "utf-8",
|
|
5027
|
+
isBinary: false,
|
|
5028
|
+
content: sanitizeTextForJsonTransport(raw.toString("utf8")) || "(arquivo vazio)",
|
|
5029
|
+
};
|
|
5030
|
+
}
|
|
5031
|
+
async readLocalFilePreview(filePath, maxChars = 1200) {
|
|
5032
|
+
const resolved = await this.resolveReadableFilePath(filePath);
|
|
5033
|
+
const loaded = await this.loadReadableFileContent(resolved);
|
|
5034
|
+
if (loaded.isBinary) {
|
|
5035
|
+
return clipText(loaded.binaryNotice || "O arquivo parece ser binario e nao pode ser lido como texto puro.", maxChars);
|
|
5036
|
+
}
|
|
5037
|
+
return clipTextPreview(loaded.content || "(arquivo vazio)", maxChars);
|
|
5038
|
+
}
|
|
5039
|
+
async readLocalFileSnapshot(filePath, chunkSizeChars = 4000) {
|
|
5040
|
+
const resolved = await this.resolveReadableFilePath(filePath);
|
|
5041
|
+
const entryStat = await stat(resolved);
|
|
5042
|
+
const loaded = await this.loadReadableFileContent(resolved);
|
|
5043
|
+
const fileName = path.basename(resolved) || resolved;
|
|
5044
|
+
if (loaded.isBinary) {
|
|
5045
|
+
return {
|
|
5046
|
+
captured_at: new Date().toISOString(),
|
|
5047
|
+
path: filePath,
|
|
5048
|
+
resolved_path: resolved,
|
|
5049
|
+
name: fileName,
|
|
5050
|
+
mime_type: loaded.mimeType,
|
|
5051
|
+
size_bytes: entryStat.size,
|
|
5052
|
+
modified_at: entryStat.mtime.toISOString(),
|
|
5053
|
+
is_binary: true,
|
|
5054
|
+
binary_notice: loaded.binaryNotice,
|
|
5055
|
+
summary: loaded.binaryNotice || `O arquivo ${filePath} parece ser binario e nao pode ser lido como texto puro.`,
|
|
5056
|
+
};
|
|
4982
5057
|
}
|
|
4983
|
-
const content =
|
|
4984
|
-
|
|
5058
|
+
const content = loaded.content || "(arquivo vazio)";
|
|
5059
|
+
const chunks = chunkTextForTransport(content, chunkSizeChars);
|
|
5060
|
+
return {
|
|
5061
|
+
captured_at: new Date().toISOString(),
|
|
5062
|
+
path: filePath,
|
|
5063
|
+
resolved_path: resolved,
|
|
5064
|
+
name: fileName,
|
|
5065
|
+
mime_type: loaded.mimeType,
|
|
5066
|
+
size_bytes: entryStat.size,
|
|
5067
|
+
modified_at: entryStat.mtime.toISOString(),
|
|
5068
|
+
is_binary: false,
|
|
5069
|
+
encoding: loaded.encoding || "utf-8",
|
|
5070
|
+
content: chunks.length <= 1 ? content : undefined,
|
|
5071
|
+
content_char_count: content.length,
|
|
5072
|
+
chunk_size_chars: Math.max(400, Math.min(Math.round(chunkSizeChars || 4000), 32_000)),
|
|
5073
|
+
chunk_count: chunks.length,
|
|
5074
|
+
chunks,
|
|
5075
|
+
summary: `Li ${filePath} por completo (${content.length} caracteres em ${chunks.length} bloco${chunks.length === 1 ? "" : "s"}).`,
|
|
5076
|
+
};
|
|
4985
5077
|
}
|
|
4986
5078
|
async resolveReadableFilePath(filePath) {
|
|
4987
5079
|
const resolved = expandUserPath(filePath);
|
|
@@ -5052,22 +5144,55 @@ if let output = String(data: data, encoding: .utf8) {
|
|
|
5052
5144
|
}
|
|
5053
5145
|
return null;
|
|
5054
5146
|
}
|
|
5055
|
-
async
|
|
5147
|
+
async listLocalFilesSnapshot(directoryPath, limit) {
|
|
5056
5148
|
const resolved = expandUserPath(directoryPath);
|
|
5057
|
-
const
|
|
5058
|
-
const
|
|
5149
|
+
const allEntries = await readdir(resolved, { withFileTypes: true });
|
|
5150
|
+
const sortedEntries = allEntries.sort((left, right) => {
|
|
5151
|
+
if (left.isDirectory() !== right.isDirectory()) {
|
|
5152
|
+
return left.isDirectory() ? -1 : 1;
|
|
5153
|
+
}
|
|
5154
|
+
return left.name.localeCompare(right.name);
|
|
5155
|
+
});
|
|
5156
|
+
const effectiveLimit = typeof limit === "number" && Number.isFinite(limit) ? Math.max(1, Math.min(Math.round(limit), 5_000)) : null;
|
|
5157
|
+
const selectedEntries = effectiveLimit ? sortedEntries.slice(0, effectiveLimit) : sortedEntries;
|
|
5158
|
+
const items = await Promise.all(selectedEntries.map(async (entry) => {
|
|
5059
5159
|
const entryPath = path.join(resolved, entry.name);
|
|
5060
|
-
let
|
|
5160
|
+
let sizeBytes;
|
|
5161
|
+
let modifiedAt;
|
|
5061
5162
|
try {
|
|
5062
5163
|
const entryStat = await stat(entryPath);
|
|
5063
|
-
|
|
5164
|
+
if (!entry.isDirectory()) {
|
|
5165
|
+
sizeBytes = entryStat.size;
|
|
5166
|
+
}
|
|
5167
|
+
modifiedAt = entryStat.mtime.toISOString();
|
|
5064
5168
|
}
|
|
5065
5169
|
catch {
|
|
5066
|
-
|
|
5170
|
+
// Ignore stat failures and return the visible entry metadata we have.
|
|
5067
5171
|
}
|
|
5068
|
-
return
|
|
5172
|
+
return {
|
|
5173
|
+
name: entry.name,
|
|
5174
|
+
path: entryPath,
|
|
5175
|
+
kind: entry.isDirectory() ? "directory" : (entry.isFile() ? "file" : "other"),
|
|
5176
|
+
size_bytes: sizeBytes,
|
|
5177
|
+
modified_at: modifiedAt,
|
|
5178
|
+
};
|
|
5069
5179
|
}));
|
|
5070
|
-
|
|
5180
|
+
const summary = items.length === 0
|
|
5181
|
+
? `A pasta ${directoryPath} esta vazia.`
|
|
5182
|
+
: effectiveLimit && allEntries.length > items.length
|
|
5183
|
+
? `Listei ${items.length} itens visiveis em ${directoryPath} agora. A pasta tem ${allEntries.length} itens no total.`
|
|
5184
|
+
: `Listei ${items.length} itens de ${directoryPath} por completo.`;
|
|
5185
|
+
return {
|
|
5186
|
+
captured_at: new Date().toISOString(),
|
|
5187
|
+
path: directoryPath,
|
|
5188
|
+
resolved_path: resolved,
|
|
5189
|
+
name: path.basename(resolved) || resolved,
|
|
5190
|
+
item_count: items.length,
|
|
5191
|
+
total_item_count: allEntries.length,
|
|
5192
|
+
limit_applied: effectiveLimit || undefined,
|
|
5193
|
+
entries: items,
|
|
5194
|
+
summary,
|
|
5195
|
+
};
|
|
5071
5196
|
}
|
|
5072
5197
|
async countLocalFiles(directoryPath, extensions, recursive = true) {
|
|
5073
5198
|
const resolved = expandUserPath(directoryPath);
|
package/dist/runtime.js
CHANGED
|
@@ -5,6 +5,7 @@ import { NativeMacOSJobExecutor } from "./executors/native_macos.js";
|
|
|
5
5
|
import { JobCancelledError } from "./executors/shared.js";
|
|
6
6
|
import { isManagedBridgeExtensionSlug, loadManagedBridgeExtensionState, } from "./extensions.js";
|
|
7
7
|
import { LocalAutomationRuntime } from "./local_automations.js";
|
|
8
|
+
import { buildLocalToolCatalog } from "./tool_catalog.js";
|
|
8
9
|
function delay(ms) {
|
|
9
10
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
11
|
}
|
|
@@ -89,6 +90,7 @@ export class BridgeRuntime {
|
|
|
89
90
|
const metadata = {
|
|
90
91
|
...(this.config.metadata || {}),
|
|
91
92
|
installed_extensions: this.config.installedExtensions,
|
|
93
|
+
executor_type: this.config.executor.type,
|
|
92
94
|
};
|
|
93
95
|
const managedExtensions = {};
|
|
94
96
|
const connectedMessageApps = new Set();
|
|
@@ -125,9 +127,21 @@ export class BridgeRuntime {
|
|
|
125
127
|
if (Object.keys(managedExtensions).length > 0) {
|
|
126
128
|
metadata.managed_extensions = managedExtensions;
|
|
127
129
|
}
|
|
130
|
+
else {
|
|
131
|
+
delete metadata.managed_extensions;
|
|
132
|
+
}
|
|
128
133
|
if (connectedMessageApps.size > 0) {
|
|
129
134
|
metadata.connected_message_apps = Array.from(connectedMessageApps);
|
|
130
135
|
}
|
|
136
|
+
else {
|
|
137
|
+
delete metadata.connected_message_apps;
|
|
138
|
+
}
|
|
139
|
+
const toolCatalog = buildLocalToolCatalog({
|
|
140
|
+
executorType: this.config.executor.type,
|
|
141
|
+
connectedMessageApps: metadata.connected_message_apps,
|
|
142
|
+
});
|
|
143
|
+
metadata.local_tools_version = toolCatalog.version;
|
|
144
|
+
metadata.local_tools = toolCatalog.tools;
|
|
131
145
|
return metadata;
|
|
132
146
|
}
|
|
133
147
|
async sendHello(socket) {
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { BRIDGE_LOCAL_TOOLS_VERSION, } from "./types.js";
|
|
2
|
+
const NATIVE_MACOS_BASE_TOOLS = [
|
|
3
|
+
{
|
|
4
|
+
id: "desktop.apps",
|
|
5
|
+
title: "Abrir e focar apps",
|
|
6
|
+
description: "Abre aplicativos locais e traz uma janela para frente no macOS.",
|
|
7
|
+
category: "desktop",
|
|
8
|
+
mode: "control",
|
|
9
|
+
action_types: ["open_app", "focus_app"],
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: "browser.navigation",
|
|
13
|
+
title: "Abrir links e destinos web",
|
|
14
|
+
description: "Abre URLs e pesquisas locais no navegador.",
|
|
15
|
+
category: "browser",
|
|
16
|
+
mode: "control",
|
|
17
|
+
action_types: ["open_url"],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "content.notes",
|
|
21
|
+
title: "Criar notas e texto",
|
|
22
|
+
description: "Cria notas, digita texto e executa atalhos locais de escrita.",
|
|
23
|
+
category: "content",
|
|
24
|
+
mode: "write",
|
|
25
|
+
action_types: ["create_note", "type_text", "press_shortcut"],
|
|
26
|
+
requires_confirmation: true,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "desktop.capture",
|
|
30
|
+
title: "Captura de tela",
|
|
31
|
+
description: "Tira screenshot local do macOS.",
|
|
32
|
+
category: "desktop",
|
|
33
|
+
mode: "observe",
|
|
34
|
+
action_types: ["take_screenshot"],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "browser.context",
|
|
38
|
+
title: "Leitura do navegador",
|
|
39
|
+
description: "Le a pagina frontal e coleta contexto da aba atual do navegador.",
|
|
40
|
+
category: "browser",
|
|
41
|
+
mode: "observe",
|
|
42
|
+
action_types: ["read_frontmost_page", "browser_context"],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "system.apps",
|
|
46
|
+
title: "Status de apps",
|
|
47
|
+
description: "Checa app em foco, apps abertos e processos pesados.",
|
|
48
|
+
category: "system",
|
|
49
|
+
mode: "observe",
|
|
50
|
+
action_types: ["app_status"],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "filesystem.overview",
|
|
54
|
+
title: "Inspecao de arquivos e pastas",
|
|
55
|
+
description: "Inspeciona caminhos locais, lista itens e conta arquivos.",
|
|
56
|
+
category: "filesystem",
|
|
57
|
+
mode: "observe",
|
|
58
|
+
action_types: ["filesystem_inspect", "list_files", "count_files"],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "filesystem.read",
|
|
62
|
+
title: "Leitura de arquivo",
|
|
63
|
+
description: "Le arquivos de texto quando o caminho esta claro.",
|
|
64
|
+
category: "filesystem",
|
|
65
|
+
mode: "observe",
|
|
66
|
+
action_types: ["read_file"],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "system.health",
|
|
70
|
+
title: "Status do Mac",
|
|
71
|
+
description: "Coleta CPU, memoria, disco, bateria e processos de topo.",
|
|
72
|
+
category: "system",
|
|
73
|
+
mode: "observe",
|
|
74
|
+
action_types: ["system_status"],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "shell.inspect",
|
|
78
|
+
title: "Shell local",
|
|
79
|
+
description: "Roda comandos locais de consulta e diagnostico.",
|
|
80
|
+
category: "shell",
|
|
81
|
+
mode: "control",
|
|
82
|
+
action_types: ["run_shell"],
|
|
83
|
+
requires_confirmation: true,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "desktop.audio",
|
|
87
|
+
title: "Controle de volume",
|
|
88
|
+
description: "Ajusta o volume do macOS.",
|
|
89
|
+
category: "desktop",
|
|
90
|
+
mode: "control",
|
|
91
|
+
action_types: ["set_volume"],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "desktop.scroll",
|
|
95
|
+
title: "Scroll da tela",
|
|
96
|
+
description: "Rola a view ativa para cima ou para baixo.",
|
|
97
|
+
category: "desktop",
|
|
98
|
+
mode: "control",
|
|
99
|
+
action_types: ["scroll_view"],
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "visual.click",
|
|
103
|
+
title: "Clique visual guiado",
|
|
104
|
+
description: "Procura um alvo simples na tela e clica nele.",
|
|
105
|
+
category: "visual",
|
|
106
|
+
mode: "control",
|
|
107
|
+
action_types: ["click_visual_target"],
|
|
108
|
+
requires_confirmation: true,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "visual.drag",
|
|
112
|
+
title: "Arraste visual guiado",
|
|
113
|
+
description: "Arrasta um alvo visivel para outro alvo visivel.",
|
|
114
|
+
category: "visual",
|
|
115
|
+
mode: "control",
|
|
116
|
+
action_types: ["drag_visual_target"],
|
|
117
|
+
requires_confirmation: true,
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
const MOCK_TOOLS = [
|
|
121
|
+
{
|
|
122
|
+
id: "mock.runtime",
|
|
123
|
+
title: "Execucao mock",
|
|
124
|
+
description: "Simula a execucao local para testes sem efeitos reais no dispositivo.",
|
|
125
|
+
category: "desktop",
|
|
126
|
+
mode: "control",
|
|
127
|
+
action_types: [],
|
|
128
|
+
notes: "Os resultados sao sinteticos e servem apenas para desenvolvimento.",
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
const CLAWD_CURSOR_TOOLS = [
|
|
132
|
+
{
|
|
133
|
+
id: "clawd.cursor.task",
|
|
134
|
+
title: "Tarefa aberta no executor",
|
|
135
|
+
description: "Delegacao geral para o runtime Clawd Cursor executar uma tarefa no computador.",
|
|
136
|
+
category: "desktop",
|
|
137
|
+
mode: "control",
|
|
138
|
+
action_types: [],
|
|
139
|
+
requires_confirmation: true,
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
function hasConnectedMessageApp(apps, target) {
|
|
143
|
+
return apps.some((item) => item === target);
|
|
144
|
+
}
|
|
145
|
+
function buildNativeMessagingTools(connectedMessageApps) {
|
|
146
|
+
const tools = [];
|
|
147
|
+
if (hasConnectedMessageApp(connectedMessageApps, "whatsapp")) {
|
|
148
|
+
tools.push({
|
|
149
|
+
id: "messaging.whatsapp.read",
|
|
150
|
+
title: "Leitura de WhatsApp",
|
|
151
|
+
description: "Abre uma conversa no WhatsApp Web e le as mensagens visiveis.",
|
|
152
|
+
category: "messaging",
|
|
153
|
+
mode: "observe",
|
|
154
|
+
action_types: ["whatsapp_read_chat"],
|
|
155
|
+
}, {
|
|
156
|
+
id: "messaging.whatsapp.send",
|
|
157
|
+
title: "Envio de WhatsApp",
|
|
158
|
+
description: "Abre a conversa certa e envia mensagem no WhatsApp Web.",
|
|
159
|
+
category: "messaging",
|
|
160
|
+
mode: "write",
|
|
161
|
+
action_types: ["whatsapp_send_message"],
|
|
162
|
+
requires_confirmation: true,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return tools;
|
|
166
|
+
}
|
|
167
|
+
export function buildLocalToolCatalog(context) {
|
|
168
|
+
const connectedMessageApps = Array.from(new Set((context.connectedMessageApps || [])
|
|
169
|
+
.map((item) => String(item || "").trim().toLowerCase())
|
|
170
|
+
.filter(Boolean)));
|
|
171
|
+
if (context.executorType === "native-macos") {
|
|
172
|
+
return {
|
|
173
|
+
version: BRIDGE_LOCAL_TOOLS_VERSION,
|
|
174
|
+
executor_type: context.executorType,
|
|
175
|
+
tools: [
|
|
176
|
+
...NATIVE_MACOS_BASE_TOOLS,
|
|
177
|
+
...buildNativeMessagingTools(connectedMessageApps),
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (context.executorType === "clawd-cursor") {
|
|
182
|
+
return {
|
|
183
|
+
version: BRIDGE_LOCAL_TOOLS_VERSION,
|
|
184
|
+
executor_type: context.executorType,
|
|
185
|
+
tools: CLAWD_CURSOR_TOOLS,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
version: BRIDGE_LOCAL_TOOLS_VERSION,
|
|
190
|
+
executor_type: context.executorType,
|
|
191
|
+
tools: MOCK_TOOLS,
|
|
192
|
+
};
|
|
193
|
+
}
|
package/dist/types.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const BRIDGE_CONFIG_VERSION = 1;
|
|
2
|
-
export const BRIDGE_VERSION = "0.
|
|
2
|
+
export const BRIDGE_VERSION = "0.9.1";
|
|
3
3
|
export const BRIDGE_PACKAGE_NAME = "@leg3ndy/otto-bridge";
|
|
4
4
|
export const DEFAULT_API_BASE_URL = "http://localhost:8000";
|
|
5
5
|
export const DEFAULT_POLL_INTERVAL_MS = 3000;
|
|
@@ -10,3 +10,4 @@ export const DEFAULT_RECONNECT_MAX_DELAY_MS = 15000;
|
|
|
10
10
|
export const DEFAULT_EXECUTOR_TYPE = "mock";
|
|
11
11
|
export const DEFAULT_CLAWD_CURSOR_BASE_URL = "http://127.0.0.1:3847";
|
|
12
12
|
export const DEFAULT_CLAWD_CURSOR_POLL_INTERVAL_MS = 1500;
|
|
13
|
+
export const BRIDGE_LOCAL_TOOLS_VERSION = 1;
|