@phnx-labs/agents-cli 1.19.2 → 1.20.3
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/CHANGELOG.md +140 -0
- package/README.md +72 -12
- package/dist/browser.js +0 -0
- package/dist/commands/browser.js +88 -16
- package/dist/commands/cli.d.ts +14 -0
- package/dist/commands/cli.js +244 -0
- package/dist/commands/cloud.js +1 -1
- package/dist/commands/commands.js +27 -10
- package/dist/commands/computer.js +18 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/exec.js +38 -18
- package/dist/commands/factory.d.ts +3 -14
- package/dist/commands/factory.js +3 -3
- package/dist/commands/feedback.d.ts +7 -0
- package/dist/commands/feedback.js +89 -0
- package/dist/commands/helper.d.ts +12 -0
- package/dist/commands/helper.js +87 -0
- package/dist/commands/hooks.js +89 -10
- package/dist/commands/mcp.js +166 -10
- package/dist/commands/packages.js +196 -27
- package/dist/commands/permissions.js +21 -6
- package/dist/commands/plugins.js +11 -4
- package/dist/commands/profiles.d.ts +8 -0
- package/dist/commands/profiles.js +118 -5
- package/dist/commands/prune.js +39 -160
- package/dist/commands/pull.js +58 -5
- package/dist/commands/routines.js +107 -14
- package/dist/commands/rules.js +8 -4
- package/dist/commands/secrets-migrate.d.ts +24 -0
- package/dist/commands/secrets-migrate.js +198 -0
- package/dist/commands/secrets-sync.d.ts +11 -0
- package/dist/commands/secrets-sync.js +155 -0
- package/dist/commands/secrets.js +79 -46
- package/dist/commands/sessions.d.ts +28 -0
- package/dist/commands/sessions.js +98 -33
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +37 -28
- package/dist/commands/skills.js +25 -8
- package/dist/commands/subagents.js +69 -49
- package/dist/commands/teams.js +61 -10
- package/dist/commands/utils.d.ts +33 -0
- package/dist/commands/utils.js +139 -0
- package/dist/commands/versions.d.ts +4 -3
- package/dist/commands/versions.js +134 -130
- package/dist/commands/view.d.ts +6 -0
- package/dist/commands/view.js +175 -19
- package/dist/commands/workflows.js +29 -6
- package/dist/computer.js +0 -0
- package/dist/index.js +38 -6
- package/dist/lib/acp/client.js +6 -1
- package/dist/lib/acp/harnesses.js +8 -0
- package/dist/lib/agents.d.ts +4 -0
- package/dist/lib/agents.js +125 -34
- package/dist/lib/auto-pull-worker.js +18 -1
- package/dist/lib/browser/cdp.d.ts +8 -1
- package/dist/lib/browser/cdp.js +40 -3
- package/dist/lib/browser/chrome.d.ts +13 -0
- package/dist/lib/browser/chrome.js +46 -3
- package/dist/lib/browser/domain-skills.d.ts +51 -0
- package/dist/lib/browser/domain-skills.js +157 -0
- package/dist/lib/browser/drivers/local.js +45 -4
- package/dist/lib/browser/drivers/ssh.js +2 -2
- package/dist/lib/browser/ipc.d.ts +8 -1
- package/dist/lib/browser/ipc.js +37 -28
- package/dist/lib/browser/profiles.d.ts +16 -3
- package/dist/lib/browser/profiles.js +44 -4
- package/dist/lib/browser/service.d.ts +3 -0
- package/dist/lib/browser/service.js +40 -5
- package/dist/lib/browser/types.d.ts +11 -4
- package/dist/lib/cli-resources.d.ts +137 -0
- package/dist/lib/cli-resources.js +477 -0
- package/dist/lib/cloud/factory.d.ts +1 -1
- package/dist/lib/cloud/factory.js +1 -1
- package/dist/lib/cloud/rush.js +5 -5
- package/dist/lib/command-skills.js +0 -2
- package/dist/lib/computer-rpc.d.ts +3 -0
- package/dist/lib/computer-rpc.js +53 -0
- package/dist/lib/daemon.js +20 -0
- package/dist/lib/events.d.ts +16 -2
- package/dist/lib/events.js +33 -2
- package/dist/lib/exec.d.ts +42 -13
- package/dist/lib/exec.js +127 -33
- package/dist/lib/help.js +11 -5
- package/dist/lib/hooks/cache.d.ts +38 -0
- package/dist/lib/hooks/cache.js +242 -0
- package/dist/lib/hooks/profile.d.ts +33 -0
- package/dist/lib/hooks/profile.js +129 -0
- package/dist/lib/hooks.d.ts +0 -10
- package/dist/lib/hooks.js +246 -11
- package/dist/lib/mcp.d.ts +15 -0
- package/dist/lib/mcp.js +46 -0
- package/dist/lib/migrate.js +1 -1
- package/dist/lib/overdue.d.ts +26 -0
- package/dist/lib/overdue.js +101 -0
- package/dist/lib/permissions.d.ts +13 -0
- package/dist/lib/permissions.js +55 -1
- package/dist/lib/plugin-marketplace.js +1 -1
- package/dist/lib/plugins.js +15 -1
- package/dist/lib/profiles-presets.d.ts +26 -0
- package/dist/lib/profiles-presets.js +216 -0
- package/dist/lib/profiles.d.ts +34 -0
- package/dist/lib/profiles.js +112 -1
- package/dist/lib/resources/mcp.js +37 -0
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/rotate.js +10 -4
- package/dist/lib/routines-format.d.ts +47 -0
- package/dist/lib/routines-format.js +194 -0
- package/dist/lib/routines.d.ts +8 -2
- package/dist/lib/routines.js +34 -14
- package/dist/lib/runner.js +83 -15
- package/dist/lib/scheduler.js +8 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -9
- package/dist/lib/secrets/bundles.d.ts +34 -17
- package/dist/lib/secrets/bundles.js +210 -36
- package/dist/lib/secrets/index.d.ts +49 -30
- package/dist/lib/secrets/index.js +126 -115
- package/dist/lib/secrets/install-helper.d.ts +45 -0
- package/dist/lib/secrets/install-helper.js +165 -0
- package/dist/lib/secrets/linux.js +4 -4
- package/dist/lib/secrets/sync.d.ts +56 -0
- package/dist/lib/secrets/sync.js +180 -0
- package/dist/lib/session/active.d.ts +8 -0
- package/dist/lib/session/active.js +3 -2
- package/dist/lib/session/db.d.ts +0 -4
- package/dist/lib/session/db.js +0 -26
- package/dist/lib/session/parse.d.ts +1 -0
- package/dist/lib/session/parse.js +44 -0
- package/dist/lib/session/render.js +4 -4
- package/dist/lib/session/types.d.ts +2 -2
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +5 -2
- package/dist/lib/shims.js +70 -38
- package/dist/lib/state.d.ts +14 -2
- package/dist/lib/state.js +51 -20
- package/dist/lib/teams/agents.d.ts +5 -4
- package/dist/lib/teams/agents.js +48 -22
- package/dist/lib/teams/api.d.ts +2 -1
- package/dist/lib/teams/api.js +4 -3
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +153 -3
- package/dist/lib/teams/summarizer.js +18 -2
- package/dist/lib/teams/worktree.js +14 -3
- package/dist/lib/types.d.ts +63 -4
- package/dist/lib/types.js +8 -3
- package/dist/lib/usage.d.ts +27 -2
- package/dist/lib/usage.js +100 -17
- package/dist/lib/versions.d.ts +45 -3
- package/dist/lib/versions.js +455 -60
- package/package.json +15 -14
- package/scripts/install-helper.js +97 -0
- package/scripts/postinstall.js +16 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/npm-shrinkwrap.json +0 -3162
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain-skill discovery for `agents browser start`.
|
|
3
|
+
*
|
|
4
|
+
* When a browser task opens a URL, look up a site-specific SKILL.md from
|
|
5
|
+
* `~/.agents/skills/browser/domain-skills/<dir>/SKILL.md` and surface its
|
|
6
|
+
* contents so the calling agent gets per-site operating instructions
|
|
7
|
+
* (selectors, gotchas, sign-in quirks) before it starts driving the page.
|
|
8
|
+
*
|
|
9
|
+
* Matching is intentionally simple: derive the hostname's second-level
|
|
10
|
+
* label (e.g. `perplexity` from `perplexity.ai`, `slack` from `app.slack.com`)
|
|
11
|
+
* and look for a directory of the same name. If the user wants a different
|
|
12
|
+
* mapping (e.g. `mail.google.com` -> `gmail/`), they can pin it via a
|
|
13
|
+
* `domains: [...]` array in the SKILL.md frontmatter; that override beats
|
|
14
|
+
* the directory-name match.
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from 'fs';
|
|
17
|
+
import * as os from 'os';
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
/** Where domain-skills live. Override via $AGENTS_BROWSER_DOMAIN_SKILLS_DIR for tests. */
|
|
20
|
+
export function domainSkillsRoot() {
|
|
21
|
+
const override = process.env.AGENTS_BROWSER_DOMAIN_SKILLS_DIR;
|
|
22
|
+
if (override)
|
|
23
|
+
return override;
|
|
24
|
+
return path.join(os.homedir(), '.agents', 'skills', 'browser', 'domain-skills');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Derive match candidates from a hostname. Order matters — earlier candidates
|
|
28
|
+
* are tried first.
|
|
29
|
+
*
|
|
30
|
+
* Examples:
|
|
31
|
+
* perplexity.ai -> ['perplexity.ai', 'perplexity']
|
|
32
|
+
* app.slack.com -> ['app.slack.com', 'slack.com', 'slack']
|
|
33
|
+
* mail.google.com -> ['mail.google.com', 'google.com', 'google', 'mail']
|
|
34
|
+
* higgsfield.ai -> ['higgsfield.ai', 'higgsfield']
|
|
35
|
+
*/
|
|
36
|
+
export function hostnameMatchCandidates(hostname) {
|
|
37
|
+
const cleaned = hostname.toLowerCase().replace(/^www\./, '');
|
|
38
|
+
if (!cleaned)
|
|
39
|
+
return [];
|
|
40
|
+
const parts = cleaned.split('.').filter(Boolean);
|
|
41
|
+
const out = new Set();
|
|
42
|
+
out.add(cleaned);
|
|
43
|
+
// Progressive label-stripping from the left: app.slack.com -> slack.com.
|
|
44
|
+
for (let i = 1; i < parts.length; i++) {
|
|
45
|
+
out.add(parts.slice(i).join('.'));
|
|
46
|
+
}
|
|
47
|
+
// Second-level label without TLD: app.slack.com -> slack, perplexity.ai -> perplexity.
|
|
48
|
+
if (parts.length >= 2) {
|
|
49
|
+
out.add(parts[parts.length - 2]);
|
|
50
|
+
}
|
|
51
|
+
// First label too, so mail.google.com can resolve a `mail` dir if that's how
|
|
52
|
+
// the user organized their skills. Last so explicit second-level wins.
|
|
53
|
+
if (parts.length >= 2) {
|
|
54
|
+
out.add(parts[0]);
|
|
55
|
+
}
|
|
56
|
+
return Array.from(out);
|
|
57
|
+
}
|
|
58
|
+
/** Parse a SKILL.md's frontmatter `domains:` list, if any. Best-effort, no schema. */
|
|
59
|
+
function parseDomainsFrontmatter(content) {
|
|
60
|
+
// Frontmatter must be at file start: ---\n...\n---\n
|
|
61
|
+
if (!content.startsWith('---'))
|
|
62
|
+
return [];
|
|
63
|
+
const end = content.indexOf('\n---', 3);
|
|
64
|
+
if (end < 0)
|
|
65
|
+
return [];
|
|
66
|
+
const fm = content.slice(3, end);
|
|
67
|
+
// Inline array form: domains: [a, b, c]
|
|
68
|
+
const inline = fm.match(/^domains:\s*\[([^\]]*)\]/m);
|
|
69
|
+
if (inline) {
|
|
70
|
+
return inline[1]
|
|
71
|
+
.split(',')
|
|
72
|
+
.map((s) => s.trim().replace(/^["']|["']$/g, '').toLowerCase())
|
|
73
|
+
.filter(Boolean);
|
|
74
|
+
}
|
|
75
|
+
// Block list form:
|
|
76
|
+
// domains:
|
|
77
|
+
// - a
|
|
78
|
+
// - b
|
|
79
|
+
const block = fm.match(/^domains:\s*\n((?:\s+-\s+\S+\n?)+)/m);
|
|
80
|
+
if (block) {
|
|
81
|
+
return block[1]
|
|
82
|
+
.split('\n')
|
|
83
|
+
.map((line) => line.replace(/^\s*-\s*/, '').trim().replace(/^["']|["']$/g, '').toLowerCase())
|
|
84
|
+
.filter(Boolean);
|
|
85
|
+
}
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve a URL to its matching domain-skill, or null if none.
|
|
90
|
+
*
|
|
91
|
+
* Two-pass strategy:
|
|
92
|
+
* 1. Index every SKILL.md in the root and read its `domains:` frontmatter.
|
|
93
|
+
* If any pinned domain matches a candidate, return that skill.
|
|
94
|
+
* 2. Fall back to directory-name match against the candidate list.
|
|
95
|
+
*
|
|
96
|
+
* Errors (missing root, unreadable file, invalid URL) are swallowed and
|
|
97
|
+
* yield null — domain-skill discovery must never break browser start.
|
|
98
|
+
*/
|
|
99
|
+
export function resolveDomainSkill(url) {
|
|
100
|
+
let hostname;
|
|
101
|
+
try {
|
|
102
|
+
hostname = new URL(url).hostname;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
if (!hostname)
|
|
108
|
+
return null;
|
|
109
|
+
const root = domainSkillsRoot();
|
|
110
|
+
let entries;
|
|
111
|
+
try {
|
|
112
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const candidates = hostnameMatchCandidates(hostname);
|
|
118
|
+
if (candidates.length === 0)
|
|
119
|
+
return null;
|
|
120
|
+
const candidateSet = new Set(candidates);
|
|
121
|
+
const indexed = [];
|
|
122
|
+
for (const e of entries) {
|
|
123
|
+
if (!e.isDirectory())
|
|
124
|
+
continue;
|
|
125
|
+
const skillPath = path.join(root, e.name, 'SKILL.md');
|
|
126
|
+
let content;
|
|
127
|
+
try {
|
|
128
|
+
content = fs.readFileSync(skillPath, 'utf-8');
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
indexed.push({
|
|
134
|
+
name: e.name,
|
|
135
|
+
skillPath,
|
|
136
|
+
content,
|
|
137
|
+
pinned: parseDomainsFrontmatter(content),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
// Pass 1: explicit `domains:` overrides.
|
|
141
|
+
for (const s of indexed) {
|
|
142
|
+
for (const d of s.pinned) {
|
|
143
|
+
if (candidateSet.has(d)) {
|
|
144
|
+
return { name: s.name, path: s.skillPath, content: s.content, hostname };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Pass 2: directory-name match, walking candidates in priority order.
|
|
149
|
+
const byName = new Map(indexed.map((s) => [s.name.toLowerCase(), s]));
|
|
150
|
+
for (const c of candidates) {
|
|
151
|
+
const hit = byName.get(c);
|
|
152
|
+
if (hit) {
|
|
153
|
+
return { name: hit.name, path: hit.skillPath, content: hit.content, hostname };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
@@ -1,6 +1,27 @@
|
|
|
1
|
+
import * as net from 'net';
|
|
1
2
|
import { CDPClient, discoverBrowserWsUrl, verifyBrowserIdentity } from '../cdp.js';
|
|
2
3
|
import { launchBrowser, getPortOccupant } from '../chrome.js';
|
|
3
4
|
import { parseEndpointUrl } from '../profiles.js';
|
|
5
|
+
/**
|
|
6
|
+
* Cheap TCP-level "is something bound here?" probe. Used as a fallback when
|
|
7
|
+
* `getPortOccupant()` (lsof-based) misses the process — Comet and other
|
|
8
|
+
* Electron apps sometimes hold a TCP socket that lsof's `-sTCP:LISTEN` filter
|
|
9
|
+
* doesn't report. If anything ACKs the connection, we treat the port as taken
|
|
10
|
+
* and surface a friendly error instead of silently auto-launching a fresh
|
|
11
|
+
* browser that would then conflict.
|
|
12
|
+
*/
|
|
13
|
+
async function probeTcpBound(port, host = '127.0.0.1', timeoutMs = 500) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
const sock = net.createConnection({ port, host });
|
|
16
|
+
const cleanup = () => {
|
|
17
|
+
sock.removeAllListeners();
|
|
18
|
+
sock.destroy();
|
|
19
|
+
};
|
|
20
|
+
const timer = setTimeout(() => { cleanup(); resolve(false); }, timeoutMs);
|
|
21
|
+
sock.once('connect', () => { clearTimeout(timer); cleanup(); resolve(true); });
|
|
22
|
+
sock.once('error', () => { clearTimeout(timer); cleanup(); resolve(false); });
|
|
23
|
+
});
|
|
24
|
+
}
|
|
4
25
|
/**
|
|
5
26
|
* Local-port listeners we refuse to attach through. These forward CDP traffic
|
|
6
27
|
* to a remote host — silently using them would let a `cdp://127.0.0.1:N`
|
|
@@ -34,7 +55,7 @@ export async function connectLocal(endpoint, profile) {
|
|
|
34
55
|
`ssh:// endpoint to drive the remote browser explicitly.`);
|
|
35
56
|
}
|
|
36
57
|
try {
|
|
37
|
-
const { wsUrl, browser } = await discoverBrowserWsUrl(port);
|
|
58
|
+
const { wsUrl, browser } = await discoverBrowserWsUrl(port, 'localhost', profile.name);
|
|
38
59
|
verifyBrowserIdentity(browser, profile.browser, port);
|
|
39
60
|
const cdp = new CDPClient();
|
|
40
61
|
await cdp.connect(wsUrl);
|
|
@@ -55,11 +76,31 @@ export async function connectLocal(endpoint, profile) {
|
|
|
55
76
|
`(\`kill ${occupant.pid}\`) or restart it with \`--remote-debugging-port=${port}\` ` +
|
|
56
77
|
`so profile "${profile.name}" can attach.`);
|
|
57
78
|
}
|
|
79
|
+
// lsof-based detection misses some Electron-family processes (Comet, custom
|
|
80
|
+
// chrome wrappers). Cheap TCP probe as a safety net: if something ACKs a
|
|
81
|
+
// connect, the port is bound — bail loudly with the profile name + endpoint
|
|
82
|
+
// rather than silently launching a duplicate browser.
|
|
83
|
+
if (await probeTcpBound(port)) {
|
|
84
|
+
throw new Error(`Profile "${profile.name}" is configured for cdp://127.0.0.1:${port}, ` +
|
|
85
|
+
`but something is already bound to that port without serving the Chrome ` +
|
|
86
|
+
`DevTools Protocol. If that's your browser running without remote debugging, ` +
|
|
87
|
+
`quit it and relaunch with \`--remote-debugging-port=${port}\`. Otherwise, ` +
|
|
88
|
+
`update the profile to a free port (\`agents browser profiles list\`).`);
|
|
89
|
+
}
|
|
58
90
|
const newPort = port;
|
|
59
91
|
const chromeOpts = { ...profile.chrome, viewport: profile.viewport };
|
|
60
|
-
|
|
92
|
+
let launched;
|
|
93
|
+
try {
|
|
94
|
+
launched = await launchBrowser(profile.name, profile.browser, newPort, chromeOpts, profile.secrets, profile.binary, profile.electron === true);
|
|
95
|
+
}
|
|
96
|
+
catch (launchErr) {
|
|
97
|
+
const reason = launchErr instanceof Error ? launchErr.message : String(launchErr);
|
|
98
|
+
throw new Error(`Could not start ${profile.browser} for profile "${profile.name}" on port ${newPort}: ${reason}. ` +
|
|
99
|
+
`Check that the browser binary is installed (\`agents browser profiles list\`) and ` +
|
|
100
|
+
`that no other process is holding the port.`);
|
|
101
|
+
}
|
|
61
102
|
const cdp = new CDPClient();
|
|
62
|
-
await cdp.connect(wsUrl);
|
|
63
|
-
return { cdp, port: newPort, pid };
|
|
103
|
+
await cdp.connect(launched.wsUrl);
|
|
104
|
+
return { cdp, port: newPort, pid: launched.pid };
|
|
64
105
|
}
|
|
65
106
|
}
|
|
@@ -63,7 +63,7 @@ export async function connectSSH(endpoint, profile) {
|
|
|
63
63
|
tunnel.kill();
|
|
64
64
|
throw new Error(`SSH tunnel failed to establish to ${host}`);
|
|
65
65
|
}
|
|
66
|
-
const { wsUrl, browser } = await discoverBrowserWsUrl(localPort);
|
|
66
|
+
const { wsUrl, browser } = await discoverBrowserWsUrl(localPort, 'localhost', profile.name);
|
|
67
67
|
try {
|
|
68
68
|
verifyBrowserIdentity(browser, profile.browser, remotePort, host);
|
|
69
69
|
}
|
|
@@ -180,7 +180,7 @@ async function ensureRemoteBrowser(user, host, browserType, port, customBinary)
|
|
|
180
180
|
const remoteCmd = [
|
|
181
181
|
shellQuote(browserPath),
|
|
182
182
|
`--remote-debugging-port=${port}`,
|
|
183
|
-
shellQuote(
|
|
183
|
+
shellQuote(`--remote-allow-origins=http://127.0.0.1:${port}`),
|
|
184
184
|
'--disable-background-timer-throttling',
|
|
185
185
|
`--user-data-dir=/tmp/agents-browser-${port}`,
|
|
186
186
|
'</dev/null >/dev/null 2>&1 &',
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { BrowserService } from './service.js';
|
|
2
2
|
import type { IPCRequest, IPCResponse } from './types.js';
|
|
3
|
+
export interface IPCRequestOptions {
|
|
4
|
+
autoStartDaemon?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare class BrowserDaemonNotRunningError extends Error {
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
9
|
+
export declare function formatBrowserDaemonNotRunningError(): string;
|
|
3
10
|
export declare function getSocketPath(): string;
|
|
4
11
|
export declare class BrowserIPCServer {
|
|
5
12
|
private server;
|
|
@@ -9,4 +16,4 @@ export declare class BrowserIPCServer {
|
|
|
9
16
|
stop(): Promise<void>;
|
|
10
17
|
private handleRequest;
|
|
11
18
|
}
|
|
12
|
-
export declare function sendIPCRequest(request: IPCRequest): Promise<IPCResponse>;
|
|
19
|
+
export declare function sendIPCRequest(request: IPCRequest, opts?: IPCRequestOptions): Promise<IPCResponse>;
|
package/dist/lib/browser/ipc.js
CHANGED
|
@@ -5,9 +5,32 @@ import { getHelpersDir } from '../state.js';
|
|
|
5
5
|
import { startDaemon } from '../daemon.js';
|
|
6
6
|
import { getCliVersion } from '../version.js';
|
|
7
7
|
const SOCKET_NAME = 'browser.sock';
|
|
8
|
+
export class BrowserDaemonNotRunningError extends Error {
|
|
9
|
+
constructor() {
|
|
10
|
+
super(formatBrowserDaemonNotRunningError());
|
|
11
|
+
this.name = 'BrowserDaemonNotRunningError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function formatBrowserDaemonNotRunningError() {
|
|
15
|
+
return [
|
|
16
|
+
'Browser daemon not running.',
|
|
17
|
+
'Start it with: agents browser start (auto-picks an installed browser)',
|
|
18
|
+
'Or pin a profile: agents browser start --profile <name>',
|
|
19
|
+
'List profiles: agents browser profiles list',
|
|
20
|
+
].join('\n');
|
|
21
|
+
}
|
|
8
22
|
export function getSocketPath() {
|
|
9
23
|
return path.join(getHelpersDir(), 'browser', SOCKET_NAME);
|
|
10
24
|
}
|
|
25
|
+
async function waitForSocket(socketPath, timeoutMs) {
|
|
26
|
+
const deadline = Date.now() + timeoutMs;
|
|
27
|
+
while (Date.now() < deadline) {
|
|
28
|
+
if (fs.existsSync(socketPath))
|
|
29
|
+
return;
|
|
30
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
31
|
+
}
|
|
32
|
+
throw new Error('Timeout waiting for browser daemon socket');
|
|
33
|
+
}
|
|
11
34
|
export class BrowserIPCServer {
|
|
12
35
|
server = null;
|
|
13
36
|
service;
|
|
@@ -107,12 +130,14 @@ export class BrowserIPCServer {
|
|
|
107
130
|
taskName: request.taskName,
|
|
108
131
|
url: request.url,
|
|
109
132
|
endpointName: request.endpoint,
|
|
133
|
+
skipDomainSkill: request.skipDomainSkill,
|
|
110
134
|
});
|
|
111
135
|
return {
|
|
112
136
|
ok: true,
|
|
113
137
|
task: result.name,
|
|
114
138
|
tabId: result.tabId,
|
|
115
139
|
windowTargetId: result.windowId,
|
|
140
|
+
skill: result.skill,
|
|
116
141
|
};
|
|
117
142
|
}
|
|
118
143
|
case 'done': {
|
|
@@ -411,8 +436,8 @@ async function maybeWarnVersionMismatch() {
|
|
|
411
436
|
// itself a hint, but a noisy one. Stay silent on this path.
|
|
412
437
|
}
|
|
413
438
|
}
|
|
414
|
-
export async function sendIPCRequest(request) {
|
|
415
|
-
const result = await sendRawIPCRequest(request);
|
|
439
|
+
export async function sendIPCRequest(request, opts = {}) {
|
|
440
|
+
const result = await sendRawIPCRequest(request, opts);
|
|
416
441
|
// Run the version check after the user's request returns — keeps the
|
|
417
442
|
// critical path zero-overhead and ensures `start` doesn't get blocked
|
|
418
443
|
// on a daemon-restart warning that the user hasn't read yet.
|
|
@@ -421,38 +446,18 @@ export async function sendIPCRequest(request) {
|
|
|
421
446
|
}
|
|
422
447
|
return result;
|
|
423
448
|
}
|
|
424
|
-
async function sendRawIPCRequest(request) {
|
|
449
|
+
async function sendRawIPCRequest(request, opts = {}) {
|
|
425
450
|
const socketPath = getSocketPath();
|
|
451
|
+
const autoStartDaemon = opts.autoStartDaemon ?? true;
|
|
426
452
|
if (!fs.existsSync(socketPath)) {
|
|
453
|
+
if (!autoStartDaemon) {
|
|
454
|
+
throw new BrowserDaemonNotRunningError();
|
|
455
|
+
}
|
|
427
456
|
await fs.promises.mkdir(path.dirname(socketPath), { recursive: true, mode: 0o700 });
|
|
428
457
|
await fs.promises.chmod(path.dirname(socketPath), 0o700);
|
|
429
458
|
startDaemon();
|
|
430
459
|
if (!fs.existsSync(socketPath)) {
|
|
431
|
-
await
|
|
432
|
-
const socketDir = path.dirname(socketPath);
|
|
433
|
-
const socketName = path.basename(socketPath);
|
|
434
|
-
const watcher = fs.watch(socketDir, (_event, file) => {
|
|
435
|
-
if (file === socketName) {
|
|
436
|
-
clearTimeout(timeout);
|
|
437
|
-
watcher.close();
|
|
438
|
-
resolve();
|
|
439
|
-
}
|
|
440
|
-
});
|
|
441
|
-
watcher.on('error', (error) => {
|
|
442
|
-
clearTimeout(timeout);
|
|
443
|
-
watcher.close();
|
|
444
|
-
reject(error);
|
|
445
|
-
});
|
|
446
|
-
const timeout = setTimeout(() => {
|
|
447
|
-
watcher.close();
|
|
448
|
-
reject(new Error('Timeout waiting for browser daemon socket'));
|
|
449
|
-
}, 6000);
|
|
450
|
-
if (fs.existsSync(socketPath)) {
|
|
451
|
-
clearTimeout(timeout);
|
|
452
|
-
watcher.close();
|
|
453
|
-
resolve();
|
|
454
|
-
}
|
|
455
|
-
});
|
|
460
|
+
await waitForSocket(socketPath, 6000);
|
|
456
461
|
}
|
|
457
462
|
if (!fs.existsSync(socketPath)) {
|
|
458
463
|
throw new Error('Failed to start browser daemon');
|
|
@@ -475,6 +480,10 @@ async function sendRawIPCRequest(request) {
|
|
|
475
480
|
}
|
|
476
481
|
});
|
|
477
482
|
socket.on('error', (err) => {
|
|
483
|
+
if (!autoStartDaemon && (err.code === 'ENOENT' || err.code === 'ECONNREFUSED')) {
|
|
484
|
+
reject(new BrowserDaemonNotRunningError());
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
478
487
|
reject(new Error(`IPC error: ${err.message}`));
|
|
479
488
|
});
|
|
480
489
|
socket.on('close', () => {
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import type { BrowserProfile } from './types.js';
|
|
2
2
|
export type { BrowserProfile } from './types.js';
|
|
3
|
+
export declare const DEFAULT_BROWSER_PROFILE_NAME = "default";
|
|
3
4
|
export declare function getBrowserRuntimeDir(): string;
|
|
4
5
|
export declare function getProfileRuntimeDir(name: string): string;
|
|
5
6
|
export declare function listProfiles(): Promise<BrowserProfile[]>;
|
|
6
7
|
export declare function getProfile(name: string): Promise<BrowserProfile | null>;
|
|
8
|
+
/**
|
|
9
|
+
* Ensure a `default` profile exists, auto-picking the first installed
|
|
10
|
+
* Chromium-family browser per the platform priority list in chrome.ts.
|
|
11
|
+
*
|
|
12
|
+
* Re-uses an existing `default` profile as-is (we don't second-guess the user
|
|
13
|
+
* if they've already customized it). On first run we walk the priority list
|
|
14
|
+
* (macOS: chrome > brave > edge > chromium > comet; Linux: chrome > chromium >
|
|
15
|
+
* brave > edge; Windows: edge > chrome > brave) and pin the profile to the
|
|
16
|
+
* first match. Throws an actionable error if none of those binaries are
|
|
17
|
+
* installed so the user knows exactly which browsers we'd accept.
|
|
18
|
+
*/
|
|
19
|
+
export declare function ensureDefaultBrowserProfile(): Promise<BrowserProfile>;
|
|
7
20
|
/**
|
|
8
21
|
* Compute the LOCAL port a profile will occupy at runtime:
|
|
9
22
|
* - `cdp://127.0.0.1:N` → N (we listen on N directly)
|
|
@@ -52,12 +65,12 @@ export declare function resolveEndpoint(profile: BrowserProfile, endpointName?:
|
|
|
52
65
|
* Returns undefined for endpoint shapes that don't carry a port (e.g. ws:// without one).
|
|
53
66
|
*
|
|
54
67
|
* Ports are scoped by host: a `cdp://127.0.0.1:9222` profile (local Chrome on
|
|
55
|
-
* this machine) and an `ssh://
|
|
56
|
-
* point at different physical ports — the host disambiguates them.
|
|
68
|
+
* this machine) and an `ssh://remote-host:9222` profile (Comet on a remote
|
|
69
|
+
* host) point at different physical ports — the host disambiguates them.
|
|
57
70
|
*
|
|
58
71
|
* Accepts both `scheme://host:port` and `scheme://host?port=N` shapes (the
|
|
59
72
|
* latter is the documented form in `types.ts` for `ssh://`). Without this,
|
|
60
|
-
* `ssh://
|
|
73
|
+
* `ssh://remote-host?port=18805` would silently fall back to 9222 and every
|
|
61
74
|
* `?port=`-style SSH profile would collide on creation.
|
|
62
75
|
*/
|
|
63
76
|
export declare function extractConfiguredEndpoint(profile: BrowserProfile): {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import { execFileSync } from 'child_process';
|
|
3
3
|
import { getBrowserRuntimeDir as getBrowserRuntimeDirRoot, readMeta, writeMeta, } from '../state.js';
|
|
4
|
-
import { findBrowserPath } from './chrome.js';
|
|
4
|
+
import { findBrowserPath, findFirstInstalledBrowser } from './chrome.js';
|
|
5
|
+
import { DEFAULT_VIEWPORT } from './devices.js';
|
|
6
|
+
export const DEFAULT_BROWSER_PROFILE_NAME = 'default';
|
|
5
7
|
export function getBrowserRuntimeDir() {
|
|
6
8
|
return getBrowserRuntimeDirRoot();
|
|
7
9
|
}
|
|
@@ -67,6 +69,44 @@ export async function getProfile(name) {
|
|
|
67
69
|
return null;
|
|
68
70
|
return configToProfile(name, config);
|
|
69
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Ensure a `default` profile exists, auto-picking the first installed
|
|
74
|
+
* Chromium-family browser per the platform priority list in chrome.ts.
|
|
75
|
+
*
|
|
76
|
+
* Re-uses an existing `default` profile as-is (we don't second-guess the user
|
|
77
|
+
* if they've already customized it). On first run we walk the priority list
|
|
78
|
+
* (macOS: chrome > brave > edge > chromium > comet; Linux: chrome > chromium >
|
|
79
|
+
* brave > edge; Windows: edge > chrome > brave) and pin the profile to the
|
|
80
|
+
* first match. Throws an actionable error if none of those binaries are
|
|
81
|
+
* installed so the user knows exactly which browsers we'd accept.
|
|
82
|
+
*/
|
|
83
|
+
export async function ensureDefaultBrowserProfile() {
|
|
84
|
+
const existing = await getProfile(DEFAULT_BROWSER_PROFILE_NAME);
|
|
85
|
+
if (existing)
|
|
86
|
+
return existing;
|
|
87
|
+
const detected = findFirstInstalledBrowser();
|
|
88
|
+
if (!detected) {
|
|
89
|
+
throw new Error('No supported browser found. Install one of: Chrome, Brave, Edge, Chromium, or Comet, ' +
|
|
90
|
+
'then re-run `agents browser start`. Or create a profile explicitly with ' +
|
|
91
|
+
'`agents browser profiles create <name> --browser <chrome|comet|chromium|brave|edge|custom>`. ' +
|
|
92
|
+
'Note: Safari and Firefox are not supported — agents browser drives over the ' +
|
|
93
|
+
'Chrome DevTools Protocol, which they don\'t implement.');
|
|
94
|
+
}
|
|
95
|
+
const freePort = await findFreeProfilePort();
|
|
96
|
+
const profile = {
|
|
97
|
+
name: DEFAULT_BROWSER_PROFILE_NAME,
|
|
98
|
+
description: `Auto-detected ${detected.browserType} profile`,
|
|
99
|
+
browser: detected.browserType,
|
|
100
|
+
binary: detected.binary,
|
|
101
|
+
endpoints: [`cdp://127.0.0.1:${freePort}`],
|
|
102
|
+
viewport: {
|
|
103
|
+
width: DEFAULT_VIEWPORT.width,
|
|
104
|
+
height: DEFAULT_VIEWPORT.height,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
await createProfile(profile);
|
|
108
|
+
return profile;
|
|
109
|
+
}
|
|
70
110
|
/**
|
|
71
111
|
* Compute the LOCAL port a profile will occupy at runtime:
|
|
72
112
|
* - `cdp://127.0.0.1:N` → N (we listen on N directly)
|
|
@@ -259,12 +299,12 @@ export function resolveEndpoint(profile, endpointName) {
|
|
|
259
299
|
* Returns undefined for endpoint shapes that don't carry a port (e.g. ws:// without one).
|
|
260
300
|
*
|
|
261
301
|
* Ports are scoped by host: a `cdp://127.0.0.1:9222` profile (local Chrome on
|
|
262
|
-
* this machine) and an `ssh://
|
|
263
|
-
* point at different physical ports — the host disambiguates them.
|
|
302
|
+
* this machine) and an `ssh://remote-host:9222` profile (Comet on a remote
|
|
303
|
+
* host) point at different physical ports — the host disambiguates them.
|
|
264
304
|
*
|
|
265
305
|
* Accepts both `scheme://host:port` and `scheme://host?port=N` shapes (the
|
|
266
306
|
* latter is the documented form in `types.ts` for `ssh://`). Without this,
|
|
267
|
-
* `ssh://
|
|
307
|
+
* `ssh://remote-host?port=18805` would silently fall back to 9222 and every
|
|
268
308
|
* `?port=`-style SSH profile would collide on creation.
|
|
269
309
|
*/
|
|
270
310
|
export function extractConfiguredEndpoint(profile) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ResolvedDomainSkill } from './domain-skills.js';
|
|
1
2
|
import { type TabInfo, type ProfileStatus, type HistoricalTask } from './types.js';
|
|
2
3
|
import { type RefOpts, type RefNode } from './refs.js';
|
|
3
4
|
import type { TargetFilter } from './types.js';
|
|
@@ -55,12 +56,14 @@ export declare class BrowserService {
|
|
|
55
56
|
taskName?: string;
|
|
56
57
|
url?: string;
|
|
57
58
|
endpointName?: string;
|
|
59
|
+
skipDomainSkill?: boolean;
|
|
58
60
|
}): Promise<{
|
|
59
61
|
task: string;
|
|
60
62
|
name: string;
|
|
61
63
|
tabId?: string;
|
|
62
64
|
windowId?: string;
|
|
63
65
|
profile: string;
|
|
66
|
+
skill?: ResolvedDomainSkill;
|
|
64
67
|
}>;
|
|
65
68
|
stop(taskName: string): Promise<{
|
|
66
69
|
ok: boolean;
|
|
@@ -3,12 +3,13 @@ import * as os from 'os';
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import { execFile } from 'child_process';
|
|
5
5
|
import { promisify } from 'util';
|
|
6
|
-
import { CDPClient, discoverBrowserWsUrl, verifyBrowserIdentity } from './cdp.js';
|
|
6
|
+
import { BrowserCdpConnectionError, CDPClient, discoverBrowserWsUrl, verifyBrowserIdentity, } from './cdp.js';
|
|
7
7
|
import { getProfile, getProfileRuntimeDir, getBrowserRuntimeDir, listProfiles, extractConfiguredPort, resolveEndpoint, } from './profiles.js';
|
|
8
8
|
import { killChrome, getRunningChromeInfo, launchBrowser, allocatePort } from './chrome.js';
|
|
9
9
|
import { connectLocal } from './drivers/local.js';
|
|
10
10
|
import { connectSSH, shellQuote } from './drivers/ssh.js';
|
|
11
11
|
import { clearProfileRuntime } from './runtime-state.js';
|
|
12
|
+
import { resolveDomainSkill } from './domain-skills.js';
|
|
12
13
|
import { generateTaskId, generateShortId, generateTaskName, } from './types.js';
|
|
13
14
|
import { getRefs, resolveRefToCoords } from './refs.js';
|
|
14
15
|
import { clickAtCoords, hoverAtCoords, scrollAtCoords, typeText, pressKey, focusNode } from './input.js';
|
|
@@ -347,7 +348,16 @@ export class BrowserService {
|
|
|
347
348
|
const result = await this.navigate(taskName, opts.url, effectiveProfileName);
|
|
348
349
|
tabId = result.tabId;
|
|
349
350
|
}
|
|
350
|
-
|
|
351
|
+
// Domain-skill discovery: when a URL is supplied, look up site-specific
|
|
352
|
+
// operating instructions and pass them back so the calling agent can pick
|
|
353
|
+
// them up alongside the task id. Failures swallowed by resolveDomainSkill.
|
|
354
|
+
let skill;
|
|
355
|
+
if (opts.url && !opts.skipDomainSkill) {
|
|
356
|
+
const resolved = resolveDomainSkill(opts.url);
|
|
357
|
+
if (resolved)
|
|
358
|
+
skill = resolved;
|
|
359
|
+
}
|
|
360
|
+
return { task: taskId, name: taskName, tabId, profile: effectiveProfileName, skill };
|
|
351
361
|
}
|
|
352
362
|
async stop(taskName) {
|
|
353
363
|
for (const [profileName, conn] of this.connections) {
|
|
@@ -1425,7 +1435,7 @@ export class BrowserService {
|
|
|
1425
1435
|
const existingInfo = getRunningChromeInfo(effectiveProfile.name);
|
|
1426
1436
|
if (existingInfo) {
|
|
1427
1437
|
try {
|
|
1428
|
-
const { wsUrl, browser } = await discoverBrowserWsUrl(existingInfo.port);
|
|
1438
|
+
const { wsUrl, browser } = await discoverBrowserWsUrl(existingInfo.port, 'localhost', effectiveProfile.name);
|
|
1429
1439
|
verifyBrowserIdentity(browser, effectiveProfile.browser, existingInfo.port);
|
|
1430
1440
|
const cdp = new CDPClient();
|
|
1431
1441
|
await cdp.connect(wsUrl);
|
|
@@ -1487,8 +1497,14 @@ export class BrowserService {
|
|
|
1487
1497
|
};
|
|
1488
1498
|
}
|
|
1489
1499
|
if (url.protocol === 'wss:' || url.protocol === 'ws:') {
|
|
1500
|
+
const port = parseInt(url.port || (url.protocol === 'wss:' ? '443' : '80'), 10);
|
|
1490
1501
|
const cdp = new CDPClient();
|
|
1491
|
-
|
|
1502
|
+
try {
|
|
1503
|
+
await cdp.connect(endpoint);
|
|
1504
|
+
}
|
|
1505
|
+
catch {
|
|
1506
|
+
throw new BrowserCdpConnectionError(port, profile.name, url.hostname || 'localhost');
|
|
1507
|
+
}
|
|
1492
1508
|
await this.enableDomains(cdp);
|
|
1493
1509
|
return {
|
|
1494
1510
|
cdp,
|
|
@@ -1502,7 +1518,7 @@ export class BrowserService {
|
|
|
1502
1518
|
}
|
|
1503
1519
|
if (url.protocol === 'http:' || url.protocol === 'https:') {
|
|
1504
1520
|
const port = parseInt(url.port || (url.protocol === 'https:' ? '443' : '80'), 10);
|
|
1505
|
-
const { wsUrl, browser } = await discoverBrowserWsUrl(port, url.hostname);
|
|
1521
|
+
const { wsUrl, browser } = await discoverBrowserWsUrl(port, url.hostname, profile.name);
|
|
1506
1522
|
verifyBrowserIdentity(browser, profile.browser, port, url.hostname);
|
|
1507
1523
|
const cdp = new CDPClient();
|
|
1508
1524
|
await cdp.connect(wsUrl);
|
|
@@ -1685,6 +1701,25 @@ export class BrowserService {
|
|
|
1685
1701
|
targetId: tabId,
|
|
1686
1702
|
flatten: true,
|
|
1687
1703
|
}));
|
|
1704
|
+
// Inject a one-shot stealth shim before any page script runs. Chromium
|
|
1705
|
+
// unconditionally exposes navigator.webdriver = true when a remote-debug
|
|
1706
|
+
// transport is attached; Cloudflare Turnstile, hCaptcha, and similar bot
|
|
1707
|
+
// checks read that property first. For browsers agents-cli spawns the
|
|
1708
|
+
// --disable-blink-features=AutomationControlled launch flag already
|
|
1709
|
+
// covers this, but for attach-to-running profiles (the Comet / Arc /
|
|
1710
|
+
// Brave case where the user launched the browser themselves) the flag
|
|
1711
|
+
// is unavailable — Page.addScriptToEvaluateOnNewDocument is the only
|
|
1712
|
+
// lever. Non-page targets (workers, service workers) will reject these
|
|
1713
|
+
// calls; we swallow the error and keep going.
|
|
1714
|
+
try {
|
|
1715
|
+
await conn.cdp.send('Page.enable', {}, sessionId);
|
|
1716
|
+
await conn.cdp.send('Page.addScriptToEvaluateOnNewDocument', {
|
|
1717
|
+
source: "Object.defineProperty(navigator,'webdriver',{get:()=>undefined});",
|
|
1718
|
+
}, sessionId);
|
|
1719
|
+
}
|
|
1720
|
+
catch {
|
|
1721
|
+
// Target doesn't support Page domain — nothing to inject.
|
|
1722
|
+
}
|
|
1688
1723
|
conn.sessionCache.set(tabId, sessionId);
|
|
1689
1724
|
return sessionId;
|
|
1690
1725
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
export type BrowserType = 'chrome' | 'comet' | 'chromium' | 'brave' | 'edge' | 'custom';
|
|
2
2
|
/**
|
|
3
3
|
* A single named endpoint preset within a profile. Lets one profile cover
|
|
4
|
-
* the local + remote variants of the same app (e.g.
|
|
5
|
-
*
|
|
4
|
+
* the local + remote variants of the same app (e.g. an Electron app on this
|
|
5
|
+
* Mac vs. on a remote host) instead of forcing two parallel profiles.
|
|
6
6
|
*
|
|
7
7
|
* Per-endpoint overrides take precedence over profile-level fields.
|
|
8
8
|
*/
|
|
9
9
|
export interface EndpointPreset {
|
|
10
10
|
/** CDP URL — `cdp://host:port` or `ssh://host?port=N` */
|
|
11
11
|
target: string;
|
|
12
|
-
/** Override the profile-level binary (e.g.
|
|
12
|
+
/** Override the profile-level binary (e.g. a remote host has no local binary). */
|
|
13
13
|
binary?: string;
|
|
14
14
|
/** Override the profile-level targetFilter (Electron app builds may diverge). */
|
|
15
15
|
targetFilter?: string;
|
|
@@ -43,7 +43,7 @@ export interface BrowserProfile {
|
|
|
43
43
|
};
|
|
44
44
|
/** Directory holding source-side JSONL logs (e.g. ~/.rush/logs). */
|
|
45
45
|
logDir?: string;
|
|
46
|
-
/** Optional SSH host where logDir lives, e.g. "user@
|
|
46
|
+
/** Optional SSH host where logDir lives, e.g. "user@remote-host". */
|
|
47
47
|
logHost?: string;
|
|
48
48
|
}
|
|
49
49
|
/** Parsed form of `BrowserProfile.targetFilter`. */
|
|
@@ -156,6 +156,7 @@ export interface IPCRequest {
|
|
|
156
156
|
since?: string;
|
|
157
157
|
until?: string;
|
|
158
158
|
appLevel?: string;
|
|
159
|
+
skipDomainSkill?: boolean;
|
|
159
160
|
}
|
|
160
161
|
/** Subset of IPCResponse describing a recording start result. */
|
|
161
162
|
export interface RecordStartFields {
|
|
@@ -200,6 +201,12 @@ export interface IPCResponse {
|
|
|
200
201
|
uploadMode?: 'input' | 'drop' | 'chooser';
|
|
201
202
|
appLogs?: any[];
|
|
202
203
|
version?: string;
|
|
204
|
+
skill?: {
|
|
205
|
+
name: string;
|
|
206
|
+
path: string;
|
|
207
|
+
content: string;
|
|
208
|
+
hostname: string;
|
|
209
|
+
};
|
|
203
210
|
}
|
|
204
211
|
export interface ConsoleEntry {
|
|
205
212
|
level: 'log' | 'info' | 'warn' | 'error';
|