@mearl/plugin-qoder 3.0.0-beta.8 → 3.0.0
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.en.md +27 -0
- package/README.md +4 -0
- package/dist/agent.d.ts +1 -10
- package/dist/agent.js +2 -33
- package/dist/capability/bundle.json +2 -2
- package/dist/capability/runtime.cjs +80 -76
- package/docs/cloud-pairing.en.md +52 -0
- package/docs/cloud-pairing.md +2 -0
- package/package.json +6 -5
package/README.en.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @mearl/plugin-qoder
|
|
2
|
+
|
|
3
|
+
[简体中文](README.md) | [English](README.en.md)
|
|
4
|
+
|
|
5
|
+
Optional Qoder integration: local CLI/ACP agents and a Cloud Session API/SSE connector. The core does not depend on this package, register Qoder by default, read PATs, or launch the CLI. This is distributed as an independent npm package.
|
|
6
|
+
|
|
7
|
+
To use the local Agent, select Qoder in Agent Chat and install the missing components there. A Qoder capability in the selected preset takes precedence. Use a preset to enable cloud pairing.
|
|
8
|
+
|
|
9
|
+
The built `dist/capability/` contains self-contained Node modules, a digest manifest, and `preset.json`. Use the existing installation flow:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @mearl/setup preset apply ./node_modules/@mearl/plugin-qoder/dist/capability/preset.json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
If you already use other capabilities, merge the `qoder` package declaration into your existing preset: `preset apply` selects the entire preset. `npm install` alone does not grant permissions or activate capabilities. Package resources can also be distributed over trusted HTTPS URLs.
|
|
16
|
+
|
|
17
|
+
After installation, local agents are discovered through `agent.provider.qoder`. Uninstalling blocks new sessions while accepted sessions can continue and close. If sessions exist during an update, the new runtime activates only after they close.
|
|
18
|
+
|
|
19
|
+
Generate a one-time Cloud pairing URL on the agent machine:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx @mearl/plugin-qoder pair
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Follow the output to start `mearl-cloud-connector` on the browser machine, then immediately call Qoder's custom `mearl` tool to complete pairing. The browser machine must have this capability installed and supply `QODER_PERSONAL_ACCESS_TOKEN`. Credentials resolve in this order: process environment, `MEARL_ENV_FILE`, `~/.mearl/.env`, and macOS Keychain `ai.mearl.qoder.pat`. Credentials are never written to pairing URLs, registration records, or logs.
|
|
26
|
+
|
|
27
|
+
After installing the capability, you can also use `npx @mearl/setup --cloud-mode qoder --yes` with the cloud-server management commands. This package supplies both `cloud.connector.qoder` and `cloud.server.qoder`; missing installations report the missing capability explicitly. ACP permission events use the standard `acp/requestPermission` contract.
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @mearl/plugin-qoder
|
|
2
2
|
|
|
3
|
+
[简体中文](README.md) | [English](README.en.md)
|
|
4
|
+
|
|
3
5
|
可选的 Qoder 接入:本地 CLI/ACP Agent 与 Cloud Session API/SSE 连接器。核心不依赖本包,也不默认注册 Qoder、读取 PAT 或启动 CLI。本包作为独立 npm 包分发。
|
|
4
6
|
|
|
5
7
|
构建后的 `dist/capability/` 包含自包含 Node 模块、摘要清单与 `preset.json`。使用现有安装流程:
|
|
@@ -10,6 +12,8 @@ npx @mearl/setup preset apply ./node_modules/@mearl/plugin-qoder/dist/capability
|
|
|
10
12
|
|
|
11
13
|
若已有其他能力,请将这里的 `qoder` 包声明合并进现有 preset;`preset apply` 选择的是整套预设。仅 `npm install` 不会自动获得权限或激活能力。包资源同样可通过受信任 HTTPS 地址分发。
|
|
12
14
|
|
|
15
|
+
使用本地 Agent 时,可在 Agent 对话中选择 Qoder 并安装缺失组件。当前预设提供的 Qoder 能力优先,云端配对通过预设启用。
|
|
16
|
+
|
|
13
17
|
本地 Agent 在安装后通过 `agent.provider.qoder` 被发现。卸载后禁止新会话;已接受会话仍可继续和关闭。更新期间有会话时,关闭会话后才启用新运行时。
|
|
14
18
|
|
|
15
19
|
Cloud 配对在 Agent 机器生成一次性地址:
|
package/dist/agent.d.ts
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import type { AgentInstalledSkill } from '@mearl/browser-core/agentSkills';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
2
|
import type * as acp from '@agentclientprotocol/sdk';
|
|
4
3
|
import type { AgentChatImageInput, AgentChatModelInfo, AgentChatProviderDescriptor } from '@mearl/browser-core/agentChat';
|
|
5
4
|
import type { AgentProvider, AgentProviderEvent, AgentProviderListSessionsOptions, AgentProviderListSessionsResult, AgentProviderResumeResult, AgentProviderSessionOptions } from '@mearl/agent-runtime';
|
|
5
|
+
export { qoderExecutableCandidatePaths } from '@mearl/agent-runtime/execution';
|
|
6
6
|
export declare function qoderModelInfo(configOptions: acp.SessionConfigOption[] | null | undefined): AgentChatModelInfo | undefined;
|
|
7
7
|
export declare function parseQoderSkillsList(output: string, cwd: string, home?: string): AgentInstalledSkill[];
|
|
8
8
|
export declare function qoderInstallCommand(platform?: NodeJS.Platform): string;
|
|
9
|
-
interface QoderExecutableCandidateOptions {
|
|
10
|
-
env?: NodeJS.ProcessEnv;
|
|
11
|
-
homedir?: () => string;
|
|
12
|
-
platform?: NodeJS.Platform;
|
|
13
|
-
processExecPath?: string;
|
|
14
|
-
readdirSync?: typeof fs.readdirSync;
|
|
15
|
-
}
|
|
16
|
-
export declare function qoderExecutableCandidatePaths(options?: QoderExecutableCandidateOptions): string[];
|
|
17
9
|
export declare class QoderAgentProvider implements AgentProvider {
|
|
18
10
|
private readonly log;
|
|
19
11
|
private readonly clientVersion;
|
|
@@ -65,4 +57,3 @@ export declare class QoderAgentProvider implements AgentProvider {
|
|
|
65
57
|
private resolveExecutableCandidates;
|
|
66
58
|
private emit;
|
|
67
59
|
}
|
|
68
|
-
export {};
|
package/dist/agent.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
1
|
import os from 'node:os';
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
import { spawn } from 'node:child_process';
|
|
5
4
|
import { Readable, Writable } from 'node:stream';
|
|
6
|
-
import {
|
|
5
|
+
import { AgentExecutableResolver, executableFileCandidates, probeAgentExecutable, qoderExecutableCandidatePaths, } from '@mearl/agent-runtime/execution';
|
|
7
6
|
import { agentSpawnCommand, createAgentChildEnv } from '@mearl/agent-runtime/execution';
|
|
7
|
+
export { qoderExecutableCandidatePaths } from '@mearl/agent-runtime/execution';
|
|
8
8
|
const PROBE_TIMEOUT_MS = 5_000;
|
|
9
9
|
const REQUEST_TIMEOUT_MS = 30_000;
|
|
10
10
|
const MAX_MODEL_CHARS = 256;
|
|
@@ -78,18 +78,6 @@ export function parseQoderSkillsList(output, cwd, home = os.homedir()) {
|
|
|
78
78
|
finish();
|
|
79
79
|
return skills;
|
|
80
80
|
}
|
|
81
|
-
function qoderManagedExecutableCandidates(home, readdirSync = fs.readdirSync) {
|
|
82
|
-
const directory = path.join(home, '.qoder', 'bin', 'qodercli');
|
|
83
|
-
try {
|
|
84
|
-
return readdirSync(directory, { withFileTypes: true })
|
|
85
|
-
.filter(entry => entry.isFile() && entry.name.startsWith('qodercli-'))
|
|
86
|
-
.sort((left, right) => right.name.localeCompare(left.name, undefined, { numeric: true }))
|
|
87
|
-
.map(entry => path.join(directory, entry.name));
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
return [];
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
81
|
function isPermissionResponse(value) {
|
|
94
82
|
if (!value || typeof value !== 'object')
|
|
95
83
|
return false;
|
|
@@ -105,25 +93,6 @@ export function qoderInstallCommand(platform = process.platform) {
|
|
|
105
93
|
? 'npm install -g @qoder-ai/qodercli'
|
|
106
94
|
: 'curl -fsSL https://qoder.com/install | bash';
|
|
107
95
|
}
|
|
108
|
-
export function qoderExecutableCandidatePaths(options = {}) {
|
|
109
|
-
const env = options.env ?? process.env;
|
|
110
|
-
const home = (options.homedir ?? os.homedir)();
|
|
111
|
-
const platform = options.platform ?? process.platform;
|
|
112
|
-
const executableNames = platform === 'win32' ? ['qodercli.exe', 'qodercli.cmd'] : ['qodercli'];
|
|
113
|
-
return agentExecutableCandidatePaths({
|
|
114
|
-
executableNames,
|
|
115
|
-
configuredPath: env.MEARL_QODER_PATH,
|
|
116
|
-
env,
|
|
117
|
-
extraUserCandidates: [
|
|
118
|
-
...qoderManagedExecutableCandidates(home, options.readdirSync),
|
|
119
|
-
...executableNames.map(name => path.join(home, '.qoder', 'bin', name)),
|
|
120
|
-
],
|
|
121
|
-
home,
|
|
122
|
-
platform,
|
|
123
|
-
processExecPath: options.processExecPath,
|
|
124
|
-
readdirSync: options.readdirSync,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
96
|
function sessionOptions(options) {
|
|
128
97
|
const cwd = path.resolve(options.cwd || os.homedir());
|
|
129
98
|
return {
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
},
|
|
8
8
|
"resources": {
|
|
9
9
|
"runtime.cjs": {
|
|
10
|
-
"sha256": "
|
|
11
|
-
"byteLength":
|
|
10
|
+
"sha256": "1a603acc3c2e3f00922cb20f2b29cfe0a05a9a2a5d7f9388809e7a6cb52f6a14",
|
|
11
|
+
"byteLength": 1103931
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"contributions": {
|
|
@@ -302,10 +302,10 @@ function mergeDefs(...defs) {
|
|
|
302
302
|
function cloneDef(schema) {
|
|
303
303
|
return mergeDefs(schema._zod.def);
|
|
304
304
|
}
|
|
305
|
-
function getElementAtPath(obj,
|
|
306
|
-
if (!
|
|
305
|
+
function getElementAtPath(obj, path5) {
|
|
306
|
+
if (!path5)
|
|
307
307
|
return obj;
|
|
308
|
-
return
|
|
308
|
+
return path5.reduce((acc, key) => acc?.[key], obj);
|
|
309
309
|
}
|
|
310
310
|
function promiseAllObject(promisesObj) {
|
|
311
311
|
const keys = Object.keys(promisesObj);
|
|
@@ -564,11 +564,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
564
564
|
}
|
|
565
565
|
return false;
|
|
566
566
|
}
|
|
567
|
-
function prefixIssues(
|
|
567
|
+
function prefixIssues(path5, issues) {
|
|
568
568
|
return issues.map((iss) => {
|
|
569
569
|
var _a3;
|
|
570
570
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
571
|
-
iss.path.unshift(
|
|
571
|
+
iss.path.unshift(path5);
|
|
572
572
|
return iss;
|
|
573
573
|
});
|
|
574
574
|
}
|
|
@@ -1101,16 +1101,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
|
|
|
1101
1101
|
}
|
|
1102
1102
|
function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
1103
1103
|
const fieldErrors = { _errors: [] };
|
|
1104
|
-
const processError = (error63,
|
|
1104
|
+
const processError = (error63, path5 = []) => {
|
|
1105
1105
|
for (const issue2 of error63.issues) {
|
|
1106
1106
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
1107
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
1107
|
+
issue2.errors.map((issues) => processError({ issues }, [...path5, ...issue2.path]));
|
|
1108
1108
|
} else if (issue2.code === "invalid_key") {
|
|
1109
|
-
processError({ issues: issue2.issues }, [...
|
|
1109
|
+
processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
|
|
1110
1110
|
} else if (issue2.code === "invalid_element") {
|
|
1111
|
-
processError({ issues: issue2.issues }, [...
|
|
1111
|
+
processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
|
|
1112
1112
|
} else {
|
|
1113
|
-
const fullpath = [...
|
|
1113
|
+
const fullpath = [...path5, ...issue2.path];
|
|
1114
1114
|
if (fullpath.length === 0) {
|
|
1115
1115
|
fieldErrors._errors.push(mapper(issue2));
|
|
1116
1116
|
} else {
|
|
@@ -1149,17 +1149,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
|
1149
1149
|
}
|
|
1150
1150
|
function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
1151
1151
|
const result = { errors: [] };
|
|
1152
|
-
const processError = (error63,
|
|
1152
|
+
const processError = (error63, path5 = []) => {
|
|
1153
1153
|
var _a3;
|
|
1154
1154
|
for (const issue2 of error63.issues) {
|
|
1155
1155
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
1156
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
1156
|
+
issue2.errors.map((issues) => processError({ issues }, [...path5, ...issue2.path]));
|
|
1157
1157
|
} else if (issue2.code === "invalid_key") {
|
|
1158
|
-
processError({ issues: issue2.issues }, [...
|
|
1158
|
+
processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
|
|
1159
1159
|
} else if (issue2.code === "invalid_element") {
|
|
1160
|
-
processError({ issues: issue2.issues }, [...
|
|
1160
|
+
processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
|
|
1161
1161
|
} else {
|
|
1162
|
-
const fullpath = [...
|
|
1162
|
+
const fullpath = [...path5, ...issue2.path];
|
|
1163
1163
|
if (fullpath.length === 0) {
|
|
1164
1164
|
result.errors.push(mapper(issue2));
|
|
1165
1165
|
continue;
|
|
@@ -1198,8 +1198,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
|
1198
1198
|
}
|
|
1199
1199
|
function toDotPath(_path) {
|
|
1200
1200
|
const segs = [];
|
|
1201
|
-
const
|
|
1202
|
-
for (const seg of
|
|
1201
|
+
const path5 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
1202
|
+
for (const seg of path5) {
|
|
1203
1203
|
if (typeof seg === "number")
|
|
1204
1204
|
segs.push(`[${seg}]`);
|
|
1205
1205
|
else if (typeof seg === "symbol")
|
|
@@ -19231,13 +19231,13 @@ function resolveRef(ref, ctx) {
|
|
|
19231
19231
|
if (!ref.startsWith("#")) {
|
|
19232
19232
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
19233
19233
|
}
|
|
19234
|
-
const
|
|
19235
|
-
if (
|
|
19234
|
+
const path5 = ref.slice(1).split("/").filter(Boolean);
|
|
19235
|
+
if (path5.length === 0) {
|
|
19236
19236
|
return ctx.rootSchema;
|
|
19237
19237
|
}
|
|
19238
19238
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
19239
|
-
if (
|
|
19240
|
-
const key =
|
|
19239
|
+
if (path5[0] === defsKey) {
|
|
19240
|
+
const key = path5[1] === void 0 ? void 0 : decodeJSONPointerSegment(path5[1]);
|
|
19241
19241
|
if (!key || !ctx.defs[key]) {
|
|
19242
19242
|
throw new Error(`Reference not found: ${ref}`);
|
|
19243
19243
|
}
|
|
@@ -25031,9 +25031,8 @@ __export(runtime_exports, {
|
|
|
25031
25031
|
module.exports = __toCommonJS(runtime_exports);
|
|
25032
25032
|
|
|
25033
25033
|
// src/agent.ts
|
|
25034
|
-
var
|
|
25035
|
-
var
|
|
25036
|
-
var import_node_path4 = __toESM(require("node:path"), 1);
|
|
25034
|
+
var import_node_os3 = __toESM(require("node:os"), 1);
|
|
25035
|
+
var import_node_path5 = __toESM(require("node:path"), 1);
|
|
25037
25036
|
var import_node_child_process2 = require("node:child_process");
|
|
25038
25037
|
var import_node_stream = require("node:stream");
|
|
25039
25038
|
|
|
@@ -25292,6 +25291,38 @@ function isExecutableFile(candidate) {
|
|
|
25292
25291
|
}
|
|
25293
25292
|
}
|
|
25294
25293
|
|
|
25294
|
+
// ../agent-runtime/dist/qoder-executable.js
|
|
25295
|
+
var import_node_fs3 = __toESM(require("node:fs"), 1);
|
|
25296
|
+
var import_node_os2 = __toESM(require("node:os"), 1);
|
|
25297
|
+
var import_node_path4 = __toESM(require("node:path"), 1);
|
|
25298
|
+
function qoderManagedExecutableCandidates(home, readdirSync = import_node_fs3.default.readdirSync) {
|
|
25299
|
+
const directory = import_node_path4.default.join(home, ".qoder", "bin", "qodercli");
|
|
25300
|
+
try {
|
|
25301
|
+
return readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.startsWith("qodercli-")).sort((left, right) => right.name.localeCompare(left.name, void 0, { numeric: true })).map((entry) => import_node_path4.default.join(directory, entry.name));
|
|
25302
|
+
} catch {
|
|
25303
|
+
return [];
|
|
25304
|
+
}
|
|
25305
|
+
}
|
|
25306
|
+
function qoderExecutableCandidatePaths(options = {}) {
|
|
25307
|
+
const env = options.env ?? process.env;
|
|
25308
|
+
const home = (options.homedir ?? import_node_os2.default.homedir)();
|
|
25309
|
+
const platform = options.platform ?? process.platform;
|
|
25310
|
+
const executableNames = platform === "win32" ? ["qodercli.exe", "qodercli.cmd"] : ["qodercli"];
|
|
25311
|
+
return agentExecutableCandidatePaths({
|
|
25312
|
+
executableNames,
|
|
25313
|
+
configuredPath: env.MEARL_QODER_PATH,
|
|
25314
|
+
env,
|
|
25315
|
+
extraUserCandidates: [
|
|
25316
|
+
...qoderManagedExecutableCandidates(home, options.readdirSync),
|
|
25317
|
+
...executableNames.map((name) => import_node_path4.default.join(home, ".qoder", "bin", name))
|
|
25318
|
+
],
|
|
25319
|
+
home,
|
|
25320
|
+
platform,
|
|
25321
|
+
processExecPath: options.processExecPath,
|
|
25322
|
+
readdirSync: options.readdirSync
|
|
25323
|
+
});
|
|
25324
|
+
}
|
|
25325
|
+
|
|
25295
25326
|
// src/agent.ts
|
|
25296
25327
|
var PROBE_TIMEOUT_MS = 5e3;
|
|
25297
25328
|
var REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -25325,14 +25356,14 @@ function qoderModelInfo(configOptions) {
|
|
|
25325
25356
|
...displayName && displayName !== id ? { displayName } : {}
|
|
25326
25357
|
};
|
|
25327
25358
|
}
|
|
25328
|
-
function parseQoderSkillsList(output2, cwd, home =
|
|
25359
|
+
function parseQoderSkillsList(output2, cwd, home = import_node_os3.default.homedir()) {
|
|
25329
25360
|
const skills = [];
|
|
25330
25361
|
let current;
|
|
25331
25362
|
const finish = () => {
|
|
25332
25363
|
if (!current) return;
|
|
25333
25364
|
const skillPath = current.path || (current.builtIn ? "bundled" : "");
|
|
25334
|
-
const relativeToCwd = skillPath &&
|
|
25335
|
-
const inProject = Boolean(relativeToCwd) && relativeToCwd !== ".." && !relativeToCwd.startsWith(`..${
|
|
25365
|
+
const relativeToCwd = skillPath && import_node_path5.default.isAbsolute(skillPath) ? import_node_path5.default.relative(cwd, skillPath) : "";
|
|
25366
|
+
const inProject = Boolean(relativeToCwd) && relativeToCwd !== ".." && !relativeToCwd.startsWith(`..${import_node_path5.default.sep}`) && !import_node_path5.default.isAbsolute(relativeToCwd);
|
|
25336
25367
|
skills.push({
|
|
25337
25368
|
name: current.name,
|
|
25338
25369
|
path: skillPath,
|
|
@@ -25357,14 +25388,6 @@ function parseQoderSkillsList(output2, cwd, home = import_node_os2.default.homed
|
|
|
25357
25388
|
finish();
|
|
25358
25389
|
return skills;
|
|
25359
25390
|
}
|
|
25360
|
-
function qoderManagedExecutableCandidates(home, readdirSync = import_node_fs3.default.readdirSync) {
|
|
25361
|
-
const directory = import_node_path4.default.join(home, ".qoder", "bin", "qodercli");
|
|
25362
|
-
try {
|
|
25363
|
-
return readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.startsWith("qodercli-")).sort((left, right) => right.name.localeCompare(left.name, void 0, { numeric: true })).map((entry) => import_node_path4.default.join(directory, entry.name));
|
|
25364
|
-
} catch {
|
|
25365
|
-
return [];
|
|
25366
|
-
}
|
|
25367
|
-
}
|
|
25368
25391
|
function isPermissionResponse(value) {
|
|
25369
25392
|
if (!value || typeof value !== "object") return false;
|
|
25370
25393
|
const outcome = value.outcome;
|
|
@@ -25375,27 +25398,8 @@ function isPermissionResponse(value) {
|
|
|
25375
25398
|
function qoderInstallCommand(platform = process.platform) {
|
|
25376
25399
|
return platform === "win32" ? "npm install -g @qoder-ai/qodercli" : "curl -fsSL https://qoder.com/install | bash";
|
|
25377
25400
|
}
|
|
25378
|
-
function qoderExecutableCandidatePaths(options = {}) {
|
|
25379
|
-
const env = options.env ?? process.env;
|
|
25380
|
-
const home = (options.homedir ?? import_node_os2.default.homedir)();
|
|
25381
|
-
const platform = options.platform ?? process.platform;
|
|
25382
|
-
const executableNames = platform === "win32" ? ["qodercli.exe", "qodercli.cmd"] : ["qodercli"];
|
|
25383
|
-
return agentExecutableCandidatePaths({
|
|
25384
|
-
executableNames,
|
|
25385
|
-
configuredPath: env.MEARL_QODER_PATH,
|
|
25386
|
-
env,
|
|
25387
|
-
extraUserCandidates: [
|
|
25388
|
-
...qoderManagedExecutableCandidates(home, options.readdirSync),
|
|
25389
|
-
...executableNames.map((name) => import_node_path4.default.join(home, ".qoder", "bin", name))
|
|
25390
|
-
],
|
|
25391
|
-
home,
|
|
25392
|
-
platform,
|
|
25393
|
-
processExecPath: options.processExecPath,
|
|
25394
|
-
readdirSync: options.readdirSync
|
|
25395
|
-
});
|
|
25396
|
-
}
|
|
25397
25401
|
function sessionOptions(options) {
|
|
25398
|
-
const cwd =
|
|
25402
|
+
const cwd = import_node_path5.default.resolve(options.cwd || import_node_os3.default.homedir());
|
|
25399
25403
|
return {
|
|
25400
25404
|
...options,
|
|
25401
25405
|
cwd,
|
|
@@ -25620,7 +25624,7 @@ var QoderAgentProvider = class {
|
|
|
25620
25624
|
throw new Error(resolution.error);
|
|
25621
25625
|
}
|
|
25622
25626
|
const { executable } = resolution;
|
|
25623
|
-
const resolvedCwd = cwd ||
|
|
25627
|
+
const resolvedCwd = cwd || import_node_os3.default.homedir();
|
|
25624
25628
|
const output2 = await this.runSkillsList(executable, resolvedCwd);
|
|
25625
25629
|
return parseQoderSkillsList(output2, resolvedCwd);
|
|
25626
25630
|
}
|
|
@@ -26360,27 +26364,27 @@ function scrollElementInPage(direction, distance, targetLabel, containerPolicy =
|
|
|
26360
26364
|
} catch {
|
|
26361
26365
|
}
|
|
26362
26366
|
}
|
|
26363
|
-
const
|
|
26367
|
+
const path5 = [];
|
|
26364
26368
|
let cursor = candidate;
|
|
26365
|
-
while (cursor && cursor.nodeType === 1 &&
|
|
26369
|
+
while (cursor && cursor.nodeType === 1 && path5.length < 8) {
|
|
26366
26370
|
const tag = String(cursor.tagName || "").toLowerCase();
|
|
26367
26371
|
if (!tag)
|
|
26368
26372
|
break;
|
|
26369
26373
|
const parent = cursor.parentElement;
|
|
26370
26374
|
if (!parent) {
|
|
26371
|
-
|
|
26375
|
+
path5.unshift(tag);
|
|
26372
26376
|
break;
|
|
26373
26377
|
}
|
|
26374
26378
|
const siblings = Array.from(parent.children).filter((sibling) => sibling.tagName === cursor.tagName);
|
|
26375
26379
|
const index = siblings.indexOf(cursor) + 1;
|
|
26376
|
-
|
|
26380
|
+
path5.unshift(siblings.length > 1 ? tag + ":nth-of-type(" + index + ")" : tag);
|
|
26377
26381
|
cursor = parent;
|
|
26378
26382
|
if (cursor === documentRef.documentElement) {
|
|
26379
|
-
|
|
26383
|
+
path5.unshift("html");
|
|
26380
26384
|
break;
|
|
26381
26385
|
}
|
|
26382
26386
|
}
|
|
26383
|
-
return
|
|
26387
|
+
return path5.join(" > ");
|
|
26384
26388
|
}
|
|
26385
26389
|
let metrics = scrollMetrics(element);
|
|
26386
26390
|
if (!metrics)
|
|
@@ -26550,27 +26554,27 @@ function inspectElementsInPage(selector, filter, styles, maxResults, invalidSele
|
|
|
26550
26554
|
} catch {
|
|
26551
26555
|
}
|
|
26552
26556
|
}
|
|
26553
|
-
const
|
|
26557
|
+
const path5 = [];
|
|
26554
26558
|
let cursor = element;
|
|
26555
|
-
while (cursor && cursor.nodeType === 1 &&
|
|
26559
|
+
while (cursor && cursor.nodeType === 1 && path5.length < 8) {
|
|
26556
26560
|
const tag = String(cursor.tagName || "").toLowerCase();
|
|
26557
26561
|
if (!tag)
|
|
26558
26562
|
break;
|
|
26559
26563
|
const parent = cursor.parentElement;
|
|
26560
26564
|
if (!parent) {
|
|
26561
|
-
|
|
26565
|
+
path5.unshift(tag);
|
|
26562
26566
|
break;
|
|
26563
26567
|
}
|
|
26564
26568
|
const siblings = Array.from(parent.children).filter((sibling) => sibling.tagName === cursor.tagName);
|
|
26565
26569
|
const index = siblings.indexOf(cursor) + 1;
|
|
26566
|
-
|
|
26570
|
+
path5.unshift(siblings.length > 1 ? tag + ":nth-of-type(" + index + ")" : tag);
|
|
26567
26571
|
cursor = parent;
|
|
26568
26572
|
if (cursor === documentRef.documentElement) {
|
|
26569
|
-
|
|
26573
|
+
path5.unshift("html");
|
|
26570
26574
|
break;
|
|
26571
26575
|
}
|
|
26572
26576
|
}
|
|
26573
|
-
return
|
|
26577
|
+
return path5.join(" > ");
|
|
26574
26578
|
}
|
|
26575
26579
|
function readStyle(style, property) {
|
|
26576
26580
|
const cssValue = style.getPropertyValue?.(property);
|
|
@@ -26999,8 +27003,8 @@ function resolveActionTimeoutSec(action, data, fallback = 15) {
|
|
|
26999
27003
|
|
|
27000
27004
|
// src/auth.ts
|
|
27001
27005
|
var import_node_fs4 = require("node:fs");
|
|
27002
|
-
var
|
|
27003
|
-
var
|
|
27006
|
+
var import_node_os4 = require("node:os");
|
|
27007
|
+
var import_node_path6 = require("node:path");
|
|
27004
27008
|
var import_node_child_process3 = require("node:child_process");
|
|
27005
27009
|
var QODER_TOKEN_KEY = "QODER_PERSONAL_ACCESS_TOKEN";
|
|
27006
27010
|
var QODER_KEYCHAIN_SERVICE = "ai.mearl.qoder.pat";
|
|
@@ -27040,7 +27044,7 @@ function readMacKeychainToken() {
|
|
|
27040
27044
|
const securityPath = "/usr/bin/security";
|
|
27041
27045
|
if (!(0, import_node_fs4.existsSync)(securityPath)) return void 0;
|
|
27042
27046
|
try {
|
|
27043
|
-
const account = (0,
|
|
27047
|
+
const account = (0, import_node_os4.userInfo)().username;
|
|
27044
27048
|
const result = (0, import_node_child_process3.spawnSync)(
|
|
27045
27049
|
securityPath,
|
|
27046
27050
|
["find-generic-password", "-a", account, "-s", QODER_KEYCHAIN_SERVICE, "-w"],
|
|
@@ -27062,10 +27066,10 @@ function resolveQoderPersonalAccessToken(options = {}) {
|
|
|
27062
27066
|
const env = options.env ?? process.env;
|
|
27063
27067
|
const inherited = normalizeToken(env[QODER_TOKEN_KEY]);
|
|
27064
27068
|
if (inherited) return inherited;
|
|
27065
|
-
const homeDirectory = options.homeDirectory ?? (0,
|
|
27069
|
+
const homeDirectory = options.homeDirectory ?? (0, import_node_os4.homedir)();
|
|
27066
27070
|
const envFiles = [
|
|
27067
|
-
...env.MEARL_ENV_FILE?.trim() ? [(0,
|
|
27068
|
-
(0,
|
|
27071
|
+
...env.MEARL_ENV_FILE?.trim() ? [(0, import_node_path6.resolve)(env.MEARL_ENV_FILE)] : [],
|
|
27072
|
+
(0, import_node_path6.resolve)(homeDirectory, ".mearl", ".env")
|
|
27069
27073
|
];
|
|
27070
27074
|
const readFile = options.readFile ?? ((file2) => (0, import_node_fs4.readFileSync)(file2, "utf8"));
|
|
27071
27075
|
for (const file2 of new Set(envFiles)) {
|
|
@@ -27208,8 +27212,8 @@ var QoderApiClient = class {
|
|
|
27208
27212
|
token;
|
|
27209
27213
|
fetchImpl;
|
|
27210
27214
|
baseUrl;
|
|
27211
|
-
async request(
|
|
27212
|
-
const response = await this.fetchImpl(`${this.baseUrl}${
|
|
27215
|
+
async request(path5, init = {}) {
|
|
27216
|
+
const response = await this.fetchImpl(`${this.baseUrl}${path5}`, {
|
|
27213
27217
|
...init,
|
|
27214
27218
|
headers: {
|
|
27215
27219
|
Authorization: `Bearer ${this.token}`,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Qoder Cloud Agent pairing
|
|
2
|
+
|
|
3
|
+
[简体中文](cloud-pairing.md) | [English](cloud-pairing.en.md)
|
|
4
|
+
|
|
5
|
+
Use this mode when running in Qoder Cloud Agent. Other remote agents use cloud-server / WebSocket. Install the Qoder capability on the browser machine as described in this package's README. The Agent machine can generate pairing information directly with this package. Both modes share Mearl action contracts.
|
|
6
|
+
|
|
7
|
+
Qoder mode uses official Client-Side custom tools and Session event APIs, without Cloudflare Tunnel or treating Qoder sandbox public endpoints as WebSockets. Follow this sequence:
|
|
8
|
+
|
|
9
|
+
1. Check whether the Agent already exposes a Client-Side custom tool named `mearl`. If so, run:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @mearl/plugin-qoder pair
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Read the local `npx @mearl/cloud-connector start "qoder://pair/..."` command and `pair` input from output. Show the local command to the user, then immediately submit the generated `pair` input to the `mearl` custom tool. It pauses until the user runs the connection command on the browser machine and pairing completes.
|
|
16
|
+
|
|
17
|
+
2. After pairing, do not run `mearl ...` shell commands inside the Qoder sandbox for this Session's browser operations. Internally map the Skill's action, payload, browser, and timeout to custom-tool calls. Deliver ordinary Mearl results to users without exposing transport differences.
|
|
18
|
+
|
|
19
|
+
3. Without a `mearl` tool, first determine whether available Qoder PAT and Agent ID/version permit reading the complete existing `tools` array, appending the configuration below, and updating the Agent. Do not overwrite other tools. If permissions or information are missing, guide the user to add:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"type": "custom",
|
|
24
|
+
"name": "mearl",
|
|
25
|
+
"description": "Invoke a Mearl browser action on the user's paired local browser. Use action=pair only with a code generated by @mearl/plugin-qoder pair.",
|
|
26
|
+
"input_schema": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"additionalProperties": false,
|
|
29
|
+
"properties": {
|
|
30
|
+
"action": { "type": "string" },
|
|
31
|
+
"payload": { "type": "object" },
|
|
32
|
+
"browser": { "type": "string" },
|
|
33
|
+
"timeoutSec": { "type": "number", "minimum": 1 }
|
|
34
|
+
},
|
|
35
|
+
"required": ["action"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Tool configuration belongs to the Agent and affects new Sessions only. Tell users to create a new Session and restart the normal flow rather than repeatedly retrying the current Session.
|
|
41
|
+
|
|
42
|
+
4. Only if the local connector reports missing authentication, 401, or an expired PAT, follow its error to configure a Qoder PAT on the browser machine and restart. Errors provide supported credential sources and configuration. Do not ask users to send PATs to the Agent or put them in command URLs, chat, or logs. If users proactively provide a PAT and explicitly request configuration, write a supported source without repeating it.
|
|
43
|
+
|
|
44
|
+
5. `page_screenshot` prefers Qoder image-content blocks while preserving metadata. If the API version rejects image blocks, connector falls back to JSON text containing original base64 so image data is not lost.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
<a id="common"></a>
|
|
49
|
+
|
|
50
|
+
## Common issues
|
|
51
|
+
|
|
52
|
+
<a id="timeout"></a>
|
package/docs/cloud-pairing.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Qoder Cloud Agent 配对模式
|
|
2
2
|
|
|
3
|
+
[简体中文](cloud-pairing.md) | [English](cloud-pairing.en.md)
|
|
4
|
+
|
|
3
5
|
当前运行于 Qoder Cloud Agent 时使用本模式;其他远端 Agent 使用 cloud-server / WebSocket 模式。浏览器机器先按本包 README 安装 Qoder 能力,Agent 机器可直接使用本包生成配对信息;两种模式保持相同的 Mearl action 契约。
|
|
4
6
|
|
|
5
7
|
Qoder 模式使用官方 Client-Side 自定义工具和 Session 事件接口,不依赖 Cloudflare Tunnel,也不把 Qoder 沙箱公网入口当作 WebSocket。Agent 按以下顺序处理:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearl/plugin-qoder",
|
|
3
|
-
"version": "3.0.0
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Optional Qoder CLI agent and cloud connector capability for Mearl",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
|
+
"README.en.md",
|
|
19
20
|
"dist",
|
|
20
21
|
"docs",
|
|
21
22
|
"!dist/**/__tests__/**",
|
|
@@ -26,10 +27,10 @@
|
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
29
|
"@agentclientprotocol/sdk": "^1.2.1",
|
|
29
|
-
"@mearl/
|
|
30
|
-
"@mearl/
|
|
31
|
-
"@mearl/
|
|
32
|
-
"@mearl/
|
|
30
|
+
"@mearl/agent-runtime": "3.0.0",
|
|
31
|
+
"@mearl/cloud-connector": "3.0.0",
|
|
32
|
+
"@mearl/browser-core": "3.0.0",
|
|
33
|
+
"@mearl/client": "3.0.0"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"typescript": "^5.8.3",
|