@harness-mix/cli 0.1.4 → 0.1.6
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 +8 -0
- package/README.md +26 -96
- package/docs/chatgpt-web-sidebar.md +7 -1
- package/docs/cross-platform.md +15 -0
- package/docs/harness-handoff-design.md +377 -0
- package/docs/harness-management.md +28 -1
- package/docs/multi-agent-collaboration.md +9 -9
- package/docs/native-codex.md +21 -10
- package/output/native-build/harness-mix-shim.exe +0 -0
- package/output/native-build/renderer-extension.js +2596 -135
- package/package.json +20 -10
- package/scripts/chatgpt-context-ui-smoke.cjs +16 -0
- package/scripts/codex-accounts-test.cjs +81 -0
- package/scripts/codex-accounts-ui-smoke.cjs +70 -0
- package/scripts/collaboration-test.cjs +8 -3
- package/scripts/collaboration-ui-smoke.cjs +40 -26
- package/scripts/e2e-integrations.cjs +52 -0
- package/scripts/e2e-native-host.cjs +39 -0
- package/scripts/handoff-checkpoint-test.cjs +83 -0
- package/scripts/install-native.cjs +4 -1
- package/scripts/integrations-test.cjs +123 -0
- package/scripts/integrations-ui-smoke.cjs +124 -0
- package/scripts/native-protocol-test.cjs +180 -5
- package/scripts/native-secret-test.cjs +2 -2
- package/scripts/native-shim-test.cjs +20 -4
- package/scripts/native-sidebar-test.cjs +7 -0
- package/scripts/native-skill-roots-test.cjs +90 -0
- package/scripts/native-ui-smoke.cjs +58 -2
- package/scripts/publish-native-package.cjs +8 -5
- package/scripts/support/integrations-mcp-fixture.cjs +15 -0
- package/scripts/switch-harness-test.cjs +27 -1
- package/scripts/test-live-mentions.cjs +152 -0
- package/src/main/adapters/acp.js +3 -3
- package/src/main/adapters/antigravity.js +17 -17
- package/src/main/adapters/claude.js +50 -10
- package/src/main/adapters/codebuddy.js +1 -0
- package/src/main/adapters/codex-app-server.js +20 -13
- package/src/main/adapters/codex.js +25 -5
- package/src/main/adapters/cursor.js +5 -1
- package/src/main/adapters/dsh.js +9 -1
- package/src/main/adapters/grok.js +7 -3
- package/src/main/adapters/hermes.js +8 -0
- package/src/main/adapters/kiro.js +2 -1
- package/src/main/adapters/managed-mcp.js +25 -0
- package/src/main/adapters/native-acp.js +4 -4
- package/src/main/adapters/omp.js +11 -0
- package/src/main/adapters/openclaw.js +6 -0
- package/src/main/adapters/opencode.js +13 -3
- package/src/main/adapters/pi.js +5 -0
- package/src/main/adapters/qoder.js +1 -0
- package/src/main/adapters/trae.js +4 -0
- package/src/main/adapters/zcode.js +3 -0
- package/src/main/harness-adapter/manifest.js +17 -2
- package/src/main/host/collaboration.js +57 -11
- package/src/main/host/handoff-access.js +53 -0
- package/src/main/host/handoff-checkpoints.js +206 -0
- package/src/main/host/handoff-mcp.cjs +38 -0
- package/src/main/host/handoff-tools.js +13 -0
- package/src/main/host/handoff.js +28 -6
- package/src/main/host/integrations.js +339 -0
- package/src/main/host/runtime.js +95 -15
- package/src/main/host/store.js +4 -4
- package/src/main/native/codex-accounts.js +260 -0
- package/src/main/native/host.js +1 -1
- package/src/main/native/launcher.js +2 -2
- package/src/main/native/protocol.js +176 -14
- package/src/main/native/redact.js +1 -1
- package/src/main/native/rs/crates/shim/src/main.rs +155 -12
- package/src/main/native/thread-list.js +3 -0
- package/src/native-ui/desktop-control/dist/tsconfig.tsbuildinfo +1 -1
- package/src/native-ui/renderer-extension/dist/types/harness-mix-settings.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/index.d.ts +4 -0
- package/src/native-ui/renderer-extension/dist/types/index.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-agent-picker.d.ts +2 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-agent-picker.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-chatgpt-context.d.ts +36 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-chatgpt-context.d.ts.map +1 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-state.d.ts +1 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-state.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-composer-dom.d.ts +3 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-composer-dom.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-harness-handoff.d.ts +53 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-harness-handoff.d.ts.map +1 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts +11 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-integrations-client.d.ts +105 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-integrations-client.d.ts.map +1 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts +7 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts +1 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/accounts-list.d.ts +2 -0
- package/src/native-ui/renderer-extension/dist/types/settings/accounts-list.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/accounts-page.d.ts +1 -0
- package/src/native-ui/renderer-extension/dist/types/settings/accounts-page.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/integrations-page.d.ts +6 -0
- package/src/native-ui/renderer-extension/dist/types/settings/integrations-page.d.ts.map +1 -0
- package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts +5 -0
- package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts +8 -4
- package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/tsconfig.tsbuildinfo +1 -1
- package/src/native-ui/renderer-extension/src/harness-mix-settings.ts +4 -1
- package/src/native-ui/renderer-extension/src/index.ts +28 -0
- package/src/native-ui/renderer-extension/src/renderer-agent-icon.ts +4 -4
- package/src/native-ui/renderer-extension/src/renderer-agent-picker.ts +46 -6
- package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +115 -18
- package/src/native-ui/renderer-extension/src/renderer-chatgpt-context.ts +87 -0
- package/src/native-ui/renderer-extension/src/renderer-codex-account-state.ts +10 -0
- package/src/native-ui/renderer-extension/src/renderer-composer-dom.ts +10 -0
- package/src/native-ui/renderer-extension/src/renderer-harness-handoff.ts +330 -0
- package/src/native-ui/renderer-extension/src/renderer-harness-mentions.ts +556 -123
- package/src/native-ui/renderer-extension/src/renderer-integrations-client.ts +59 -0
- package/src/native-ui/renderer-extension/src/renderer-model-client.ts +17 -1
- package/src/native-ui/renderer-extension/src/renderer-settings-lifecycle.ts +2 -0
- package/src/native-ui/renderer-extension/src/settings/accounts-list.ts +16 -1
- package/src/native-ui/renderer-extension/src/settings/accounts-page.ts +32 -3
- package/src/native-ui/renderer-extension/src/settings/integrations-page.ts +1474 -0
- package/src/native-ui/renderer-extension/src/settings/localization.ts +32 -13
- package/src/native-ui/renderer-extension/src/settings/pages.ts +49 -18
- package/src/native-ui/renderer-extension/src/settings/shell.css +103 -0
- package/src/native-ui/renderer-extension/test/renderer-agent-picker.test.ts +44 -13
- package/src/native-ui/renderer-extension/test/renderer-chatgpt-context.test.ts +21 -0
- package/src/native-ui/renderer-extension/test/renderer-codex-account-state.test.ts +13 -1
- package/src/native-ui/renderer-extension/test/renderer-harness-handoff.test.ts +20 -0
- package/src/native-ui/renderer-extension/test/renderer-model-client.test.ts +9 -2
- package/src/native-ui/renderer-extension/test/settings/localization.test.ts +1 -1
- package/src/native-ui/renderer-extension/test/settings/pages.test.ts +77 -8
- package/src/native-ui/renderer-extension/test/settings/shell.test.ts +6 -0
- package/src/native-ui/shared-contracts/dist/codex-accounts.d.ts +19 -4
- package/src/native-ui/shared-contracts/dist/codex-accounts.d.ts.map +1 -1
- package/src/native-ui/shared-contracts/dist/codex-accounts.js +2 -0
- package/src/native-ui/shared-contracts/dist/codex-accounts.js.map +1 -1
- package/src/native-ui/shared-contracts/dist/index.d.ts +2 -2
- package/src/native-ui/shared-contracts/dist/index.d.ts.map +1 -1
- package/src/native-ui/shared-contracts/dist/index.js +1 -1
- package/src/native-ui/shared-contracts/dist/index.js.map +1 -1
- package/src/native-ui/shared-contracts/dist/thread-harness-switch.d.ts +29 -0
- package/src/native-ui/shared-contracts/dist/thread-harness-switch.d.ts.map +1 -1
- package/src/native-ui/shared-contracts/dist/thread-harness-switch.js +13 -0
- package/src/native-ui/shared-contracts/dist/thread-harness-switch.js.map +1 -1
- package/src/native-ui/shared-contracts/dist/tsconfig.tsbuildinfo +1 -1
- package/src/native-ui/shared-contracts/src/codex-accounts.ts +2 -0
- package/src/native-ui/shared-contracts/src/index.ts +8 -1
- package/src/native-ui/shared-contracts/src/thread-harness-switch.ts +17 -0
|
@@ -20,7 +20,7 @@ const CLAUDE_PERMISSION_MODES = [
|
|
|
20
20
|
{ id: "acceptEdits", label: "接受编辑", description: "允许文件编辑;其他受保护操作前询问" },
|
|
21
21
|
{ id: "auto", label: "自动模式", description: "由 Claude 判断权限请求" },
|
|
22
22
|
{ id: "dontAsk", label: "免询问", description: "跳过权限询问(非绕过检查)" },
|
|
23
|
-
{ id: "bypassPermissions", label: "绕过权限", description: "
|
|
23
|
+
{ id: "bypassPermissions", label: "绕过权限 (YOLO)", description: "跳过全部权限检查(YOLO 模式),自动放行工具执行", dangerous: true },
|
|
24
24
|
];
|
|
25
25
|
|
|
26
26
|
function summarizeInput(input) {
|
|
@@ -244,14 +244,25 @@ async function loadSdk() {
|
|
|
244
244
|
return import("@anthropic-ai/claude-agent-sdk");
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
function normalizeClaudePermissionMode(mode) {
|
|
248
|
+
if (mode === 'yolo' || mode === 'skip' || mode === 'dangerously-skip-permissions') {
|
|
249
|
+
return 'bypassPermissions';
|
|
250
|
+
}
|
|
251
|
+
return mode;
|
|
252
|
+
}
|
|
253
|
+
|
|
247
254
|
/** 建立一个常驻 SDK 会话(open 与 fork 共用):构造 query、启动事件泵 */
|
|
248
|
-
function spawnSession(sdk, { cwd, resumeId, newSessionId, permissionMode, modelId, effort, emit, collaboration, onPlanLimit }) {
|
|
255
|
+
function spawnSession(sdk, { cwd, resumeId, newSessionId, permissionMode, modelId, effort, emit, collaboration, managedMcp = [], onPlanLimit, isWorker = false }) {
|
|
249
256
|
const input = new MessageQueue();
|
|
257
|
+
const normalizedPermissionMode = normalizeClaudePermissionMode(permissionMode) || (isWorker ? 'bypassPermissions' : undefined);
|
|
258
|
+
const isBypass = normalizedPermissionMode === 'bypassPermissions' || isWorker;
|
|
259
|
+
|
|
250
260
|
const session = {
|
|
251
261
|
nativeSessionId: resumeId || newSessionId,
|
|
252
262
|
cwd,
|
|
253
263
|
collaborationEnabled: !!collaboration,
|
|
254
|
-
permissionMode,
|
|
264
|
+
permissionMode: normalizedPermissionMode,
|
|
265
|
+
isWorker,
|
|
255
266
|
model: undefined,
|
|
256
267
|
input,
|
|
257
268
|
query: undefined,
|
|
@@ -265,14 +276,22 @@ function spawnSession(sdk, { cwd, resumeId, newSessionId, permissionMode, modelI
|
|
|
265
276
|
prompt: input,
|
|
266
277
|
options: {
|
|
267
278
|
cwd,
|
|
268
|
-
|
|
279
|
+
mcpServers: require('./managed-mcp').namedServers(managedMcp, collaboration),
|
|
280
|
+
settingSources: ['user', 'project', 'local'],
|
|
269
281
|
...(resumeId ? { resume: resumeId } : {}),
|
|
270
282
|
...(!resumeId && newSessionId ? { sessionId: newSessionId } : {}),
|
|
271
283
|
...(effort ? { effort } : {}),
|
|
272
|
-
...(
|
|
284
|
+
...(normalizedPermissionMode ? { permissionMode: normalizedPermissionMode } : {}),
|
|
285
|
+
...(isBypass ? { allowDangerouslySkipPermissions: true } : {}),
|
|
273
286
|
...(modelId ? { model: modelId } : {}),
|
|
274
287
|
...(process.env.HARNESS_MIX_CLAUDE_EXECUTABLE ? { pathToClaudeCodeExecutable: process.env.HARNESS_MIX_CLAUDE_EXECUTABLE } : {}),
|
|
275
288
|
canUseTool: (toolName, toolInput, { signal, suggestions }) => {
|
|
289
|
+
if (toolName !== 'AskUserQuestion') {
|
|
290
|
+
const currentMode = normalizeClaudePermissionMode(session.permissionMode);
|
|
291
|
+
if (currentMode === 'bypassPermissions' || session.isWorker) {
|
|
292
|
+
return { behavior: 'allow', updatedInput: toolInput };
|
|
293
|
+
}
|
|
294
|
+
}
|
|
276
295
|
const requestId = randomUUID();
|
|
277
296
|
emit(projectApproval(requestId, toolName, toolInput, suggestions));
|
|
278
297
|
return new Promise((resolve) => {
|
|
@@ -373,17 +392,26 @@ function create() {
|
|
|
373
392
|
: { available: true, detail: "Agent SDK 就绪(内置原生 CLI;未检测到独立 claude 命令)" };
|
|
374
393
|
},
|
|
375
394
|
|
|
376
|
-
async
|
|
395
|
+
async inspectIntegrations(session) {
|
|
396
|
+
const rows = await session.query.mcpServerStatus();
|
|
397
|
+
return rows.map(row => ({ name: row.name, status: row.status, tools: (row.tools || []).map(t => t.name) }));
|
|
398
|
+
},
|
|
399
|
+
async open({ thread, emit, collaboration, managedMcp }) {
|
|
377
400
|
const sdk = await loadSdk();
|
|
401
|
+
const isWorker = Boolean(thread.parentThreadId);
|
|
402
|
+
const rawMode = thread.options?.permissionMode;
|
|
403
|
+
const permissionMode = normalizeClaudePermissionMode(rawMode) || (isWorker ? 'bypassPermissions' : undefined);
|
|
378
404
|
return spawnSession(sdk, {
|
|
379
405
|
cwd: thread.cwd,
|
|
380
406
|
resumeId: thread.restore ? thread.nativeSessionId : undefined,
|
|
381
407
|
newSessionId: thread.restore ? undefined : thread.nativeSessionId,
|
|
382
408
|
effort: thread.options?.thinking,
|
|
383
|
-
permissionMode
|
|
409
|
+
permissionMode,
|
|
410
|
+
isWorker,
|
|
384
411
|
modelId: thread.options?.model?.id,
|
|
385
412
|
emit,
|
|
386
413
|
collaboration,
|
|
414
|
+
managedMcp,
|
|
387
415
|
onPlanLimit,
|
|
388
416
|
});
|
|
389
417
|
},
|
|
@@ -406,11 +434,19 @@ function create() {
|
|
|
406
434
|
},
|
|
407
435
|
|
|
408
436
|
async cancel(session) {
|
|
437
|
+
for (const pending of session.pendingApprovals?.values() ?? []) {
|
|
438
|
+
pending.resolve({ behavior: "deny", message: "用户取消了请求", interrupt: true });
|
|
439
|
+
}
|
|
440
|
+
session.pendingApprovals?.clear();
|
|
409
441
|
// 原生优雅中断;超时后由 Host 走 close 兜底
|
|
410
442
|
await Promise.race([
|
|
411
443
|
session.query?.interrupt().catch(() => {}),
|
|
412
|
-
new Promise((r) => setTimeout(r,
|
|
444
|
+
new Promise((r) => setTimeout(r, 2_000)),
|
|
413
445
|
]);
|
|
446
|
+
if (session.state.turn) {
|
|
447
|
+
session.state.turn.resolve();
|
|
448
|
+
session.state.turn = null;
|
|
449
|
+
}
|
|
414
450
|
},
|
|
415
451
|
|
|
416
452
|
async listCommands(session) {
|
|
@@ -446,10 +482,12 @@ function create() {
|
|
|
446
482
|
const copied = await getSessionMessages(result.sessionId, { dir: source.cwd });
|
|
447
483
|
const checkpointMap = Object.fromEntries(copied.map((entry, index) => [history[index].uuid, entry.uuid]));
|
|
448
484
|
// Fork 出的新原生会话立即拉起常驻进程(resume 到新 sessionId),与 open() 同路径
|
|
485
|
+
const isWorker = Boolean(source.parentThreadId);
|
|
449
486
|
const session = spawnSession(sdk, {
|
|
450
487
|
cwd: source.cwd,
|
|
451
488
|
resumeId: result.sessionId,
|
|
452
489
|
permissionMode: source.options?.permissionMode,
|
|
490
|
+
isWorker,
|
|
453
491
|
emit,
|
|
454
492
|
onPlanLimit,
|
|
455
493
|
});
|
|
@@ -470,8 +508,9 @@ function create() {
|
|
|
470
508
|
return session.model;
|
|
471
509
|
},
|
|
472
510
|
async setPermissionMode(session, mode) {
|
|
473
|
-
|
|
474
|
-
|
|
511
|
+
const normalized = normalizeClaudePermissionMode(mode);
|
|
512
|
+
session.permissionMode = normalized;
|
|
513
|
+
await session.query?.setPermissionMode(normalized);
|
|
475
514
|
},
|
|
476
515
|
async setThinkingLevel(session, level) {
|
|
477
516
|
const models = await this.listModelsFor(session);
|
|
@@ -509,6 +548,7 @@ function create() {
|
|
|
509
548
|
};
|
|
510
549
|
}
|
|
511
550
|
|
|
551
|
+
manifest.integrations = { mcp: true, skills: { global: ['.claude/skills'], project: ['.claude/skills'], overrides: { '.claude/skills': { env: 'CLAUDE_CONFIG_DIR', suffix: 'skills' } } } };
|
|
512
552
|
module.exports = {
|
|
513
553
|
manifest,
|
|
514
554
|
create,
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
const { nativeAcp } = require('./native-acp');
|
|
2
2
|
module.exports = nativeAcp({ id: 'codebuddy', name: 'CodeBuddy', aliases: ['workbuddy', 'WorkBuddy'], args: ['--acp'] });
|
|
3
|
+
module.exports.manifest.integrations.skills = { global: ['.codebuddy/skills'], project: ['.codebuddy/skills'] };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { JsonlProcess, cliSpawn } = require('../host/jsonl');
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const shared = new Map();
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* A single native Codex app-server connection shared by every Codex thread.
|
|
@@ -8,16 +8,18 @@ let shared;
|
|
|
8
8
|
* traffic to the Adapter session that registered the native thread id.
|
|
9
9
|
*/
|
|
10
10
|
class CodexAppServer {
|
|
11
|
-
constructor(diagnostic) {
|
|
11
|
+
constructor(diagnostic, codexHome) {
|
|
12
12
|
this.refs = 0;
|
|
13
13
|
this.routes = new Map();
|
|
14
14
|
this.notifications = new Set();
|
|
15
15
|
this.diagnostics = new Set(diagnostic ? [diagnostic] : []);
|
|
16
16
|
this.closed = false;
|
|
17
|
+
this.codexHome = codexHome ? require('node:path').resolve(codexHome) : null;
|
|
17
18
|
// The Desktop-bundled CLI and the PATH CLI can be different versions.
|
|
18
19
|
const executable = process.env.HARNESS_MIX_CODEX_EXECUTABLE || process.env.CODEXHOST_STOCK_CODEX_PATH;
|
|
19
20
|
const { command, args } = executable ? { command: executable, args: ['app-server', '--listen', 'stdio://'] } : cliSpawn('codex', ['app-server', '--stdio']);
|
|
20
|
-
|
|
21
|
+
const env = { ...process.env, ...(this.codexHome ? { CODEX_HOME: this.codexHome } : {}) };
|
|
22
|
+
this.process = new JsonlProcess(command, args, { env }, {
|
|
21
23
|
onEvent: (message) => this.#notification(message),
|
|
22
24
|
onRequest: (message) => this.#request(message),
|
|
23
25
|
onDiagnostic: (line) => this.#diagnostic(line),
|
|
@@ -36,16 +38,21 @@ class CodexAppServer {
|
|
|
36
38
|
});
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
static async acquire(diagnostic) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
static async acquire(diagnostic, codexHome) {
|
|
42
|
+
const key = codexHome ? require('node:path').resolve(codexHome).toLowerCase() : '<default>';
|
|
43
|
+
let server = shared.get(key);
|
|
44
|
+
if (!server || server.closed) {
|
|
45
|
+
server = new CodexAppServer(diagnostic, codexHome);
|
|
46
|
+
server.sharedKey = key;
|
|
47
|
+
shared.set(key, server);
|
|
48
|
+
} else if (diagnostic) server.diagnostics.add(diagnostic);
|
|
49
|
+
server.refs++;
|
|
43
50
|
try {
|
|
44
|
-
await
|
|
45
|
-
return
|
|
51
|
+
await server.ready;
|
|
52
|
+
return server;
|
|
46
53
|
} catch (error) {
|
|
47
|
-
|
|
48
|
-
if (
|
|
54
|
+
server.refs--;
|
|
55
|
+
if (server.refs === 0) server.stop();
|
|
49
56
|
throw error;
|
|
50
57
|
}
|
|
51
58
|
}
|
|
@@ -78,7 +85,7 @@ class CodexAppServer {
|
|
|
78
85
|
this.routes.clear();
|
|
79
86
|
this.notifications.clear();
|
|
80
87
|
this.process.stop();
|
|
81
|
-
if (shared === this) shared
|
|
88
|
+
if (this.sharedKey && shared.get(this.sharedKey) === this) shared.delete(this.sharedKey);
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
#route(message) {
|
|
@@ -112,7 +119,7 @@ class CodexAppServer {
|
|
|
112
119
|
this.closed = true;
|
|
113
120
|
for (const route of this.routes.values()) route.onExit?.(error);
|
|
114
121
|
this.routes.clear();
|
|
115
|
-
if (shared === this) shared
|
|
122
|
+
if (this.sharedKey && shared.get(this.sharedKey) === this) shared.delete(this.sharedKey);
|
|
116
123
|
}
|
|
117
124
|
}
|
|
118
125
|
|
|
@@ -356,10 +356,11 @@ function create() {
|
|
|
356
356
|
: { available: true, detail: String(result.stdout).trim() };
|
|
357
357
|
},
|
|
358
358
|
|
|
359
|
-
async open({ thread, emit, diagnostic, collaboration }) {
|
|
360
|
-
const host = await CodexAppServer.acquire(diagnostic);
|
|
359
|
+
async open({ thread, emit, diagnostic, collaboration, managedMcp = [] }) {
|
|
360
|
+
const host = await CodexAppServer.acquire(diagnostic, thread.options?.codexHome);
|
|
361
361
|
try {
|
|
362
|
-
const
|
|
362
|
+
const servers = require('./managed-mcp').namedServers(managedMcp, collaboration);
|
|
363
|
+
const options = { ...threadOptions(thread), ...(Object.keys(servers).length ? { config: Object.fromEntries(Object.entries(servers).map(([name, value]) => [`mcp_servers.${name}`, value])) } : {}) };
|
|
363
364
|
const result = thread.restore
|
|
364
365
|
? await host.request('thread/resume', { threadId: thread.nativeSessionId, ...options })
|
|
365
366
|
: await host.request('thread/start', options);
|
|
@@ -400,8 +401,19 @@ function create() {
|
|
|
400
401
|
},
|
|
401
402
|
|
|
402
403
|
async cancel(session) {
|
|
404
|
+
for (const pending of session.pendingApprovals?.values() ?? []) {
|
|
405
|
+
pending.resolve({ action: 'decline' });
|
|
406
|
+
}
|
|
407
|
+
session.pendingApprovals?.clear();
|
|
403
408
|
if (!session.state.nativeTurnId) return;
|
|
404
|
-
await
|
|
409
|
+
await Promise.race([
|
|
410
|
+
session.host.request('turn/interrupt', { threadId: session.nativeSessionId, turnId: session.state.nativeTurnId }).catch(() => {}),
|
|
411
|
+
new Promise((r) => setTimeout(r, 2_000)),
|
|
412
|
+
]);
|
|
413
|
+
if (session.state.turn) {
|
|
414
|
+
session.state.turn.resolve();
|
|
415
|
+
session.state.turn = null;
|
|
416
|
+
}
|
|
405
417
|
},
|
|
406
418
|
|
|
407
419
|
async respond(session, requestId, response) {
|
|
@@ -521,7 +533,7 @@ function create() {
|
|
|
521
533
|
async fork(source, { emit, diagnostic, message }) {
|
|
522
534
|
const lastTurnId = message?.coreTurn?.nativeTurnRef?.turnId ?? message?.coreTurn?.nativeTurnRef?.checkpointId;
|
|
523
535
|
if (message && !lastTurnId) throw new Error('这条回复缺少 Codex 原生 Turn ID,无法精确分支');
|
|
524
|
-
const host = await CodexAppServer.acquire(diagnostic);
|
|
536
|
+
const host = await CodexAppServer.acquire(diagnostic, source.options?.codexHome);
|
|
525
537
|
try {
|
|
526
538
|
const result = await host.request('thread/fork', {
|
|
527
539
|
threadId: source.nativeSessionId,
|
|
@@ -556,4 +568,12 @@ function create() {
|
|
|
556
568
|
};
|
|
557
569
|
}
|
|
558
570
|
|
|
571
|
+
// Global discovery: ~/.agents/skills is the recommended location; $CODEX_HOME/skills
|
|
572
|
+
// (~/.codex/skills) is deprecated upstream but still loaded for backwards compatibility.
|
|
573
|
+
// https://developers.openai.com/codex/skills
|
|
574
|
+
manifest.integrations = { mcp: true, skills: {
|
|
575
|
+
global: ['.agents/skills', '.codex/skills'],
|
|
576
|
+
project: ['.agents/skills'],
|
|
577
|
+
overrides: { '.codex/skills': { env: 'CODEX_HOME', suffix: 'skills' } },
|
|
578
|
+
} };
|
|
559
579
|
module.exports = { manifest, create, projectNotification, queueRequest, usageView, modelView };
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
const { nativeAcp } = require('./native-acp');
|
|
2
|
-
module.exports = nativeAcp({ id: 'cursor-cli', name: 'Cursor', aliases: ['cursor', 'cursor-cli', 'cursorcli'], args: ['acp'] });
|
|
2
|
+
module.exports = nativeAcp({ id: 'cursor-cli', name: 'Cursor', aliases: ['cursor', 'cursor-cli', 'cursorcli'], args: ['acp'] });
|
|
3
|
+
module.exports.manifest.integrations.skills = {
|
|
4
|
+
global: ['.cursor/skills', '.agents/skills', '.claude/skills', '.codex/skills'],
|
|
5
|
+
project: ['.cursor/skills', '.agents/skills', '.claude/skills', '.codex/skills'],
|
|
6
|
+
};
|
package/src/main/adapters/dsh.js
CHANGED
|
@@ -431,7 +431,7 @@ function create() {
|
|
|
431
431
|
}).create();
|
|
432
432
|
const openWeb = web.open.bind(web);
|
|
433
433
|
web.open = async context => {
|
|
434
|
-
if (!context.collaboration) return openWeb(context);
|
|
434
|
+
if (!context.collaboration && !context.managedMcp?.length) return openWeb(context);
|
|
435
435
|
const session = await acp.open(context);
|
|
436
436
|
session.dshAcpLead = true;
|
|
437
437
|
return session;
|
|
@@ -443,4 +443,12 @@ function create() {
|
|
|
443
443
|
return web;
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
+
manifest.integrations = { mcp: true, skills: {
|
|
447
|
+
global: ['.dsh/skills', '.agents/skills'],
|
|
448
|
+
project: ['.dsh/skills', '.agents/skills'],
|
|
449
|
+
overrides: {
|
|
450
|
+
'.dsh/skills': { env: 'DSH_HOME', suffix: 'skills' },
|
|
451
|
+
'.agents/skills': { env: 'DSH_AGENTS_HOME', suffix: 'skills' },
|
|
452
|
+
},
|
|
453
|
+
} };
|
|
446
454
|
module.exports = { manifest, create, projectWireEvent, flattenCatalog };
|
|
@@ -31,7 +31,7 @@ function grokAdapter() {
|
|
|
31
31
|
return new Promise(resolve => execFile(cli.command, cli.args, { windowsHide: true, timeout: 10000 }, (error, out) => resolve({ available: !error, detail: error ? `${name} CLI 不可用` : out.trim() })));
|
|
32
32
|
},
|
|
33
33
|
async describe() { return { models: null, thinkingLevels: [], permissionModes: [] }; },
|
|
34
|
-
async open({ thread, emit, diagnostic = () => {}, collaboration }) {
|
|
34
|
+
async open({ thread, emit, diagnostic = () => {}, collaboration, managedMcp = [] }) {
|
|
35
35
|
const cli = command(args);
|
|
36
36
|
const session = { cwd: thread.cwd, nativeSessionId: null, collaborationEnabled: !!collaboration, state: { configOptions: [], models: null, modes: null, commands: [], usage: undefined, loading: true }, pendingApprovals: new Map(), tools: new Map(), emit };
|
|
37
37
|
session.process = new JsonlProcess(cli.command, cli.args, { cwd: thread.cwd }, {
|
|
@@ -105,7 +105,7 @@ function grokAdapter() {
|
|
|
105
105
|
session.state.agentCapabilities = init.agentCapabilities;
|
|
106
106
|
session.state.commands = init._meta?.availableCommands || [];
|
|
107
107
|
// L1 会话级注入:Grok 原生协议的 session/new 自带 mcpServers 槽(ACP 形态 stdio 定义)
|
|
108
|
-
const mcpServers =
|
|
108
|
+
const mcpServers = require('./managed-mcp').acpServers(managedMcp, collaboration);
|
|
109
109
|
const result = await session.process.request(thread.restore ? 'session/load' : 'session/new', { cwd: thread.cwd, mcpServers, ...(thread.restore ? { sessionId: thread.nativeSessionId } : {}) });
|
|
110
110
|
session.nativeSessionId = result.sessionId || thread.nativeSessionId;
|
|
111
111
|
session.state.configOptions = result.configOptions || result._meta?.['x.ai/sessionConfig']?.options || [];
|
|
@@ -274,4 +274,8 @@ async function doGrokCompact(session, userContext, hooks) {
|
|
|
274
274
|
});
|
|
275
275
|
hooks?.emit?.({ kind: 'completed', finalAnswer: outcome !== 'cancelled' });
|
|
276
276
|
}
|
|
277
|
-
module.exports = { ...grokAdapter(), projectUsage, parseGrokCompactionUpdate, doGrokCompact };
|
|
277
|
+
module.exports = { ...grokAdapter(), projectUsage, parseGrokCompactionUpdate, doGrokCompact };
|
|
278
|
+
module.exports.manifest.integrations = { mcp: true, skills: {
|
|
279
|
+
global: ['.grok/skills', '.agents/skills', '.claude/skills'],
|
|
280
|
+
project: ['.grok/skills', '.claude/skills'],
|
|
281
|
+
} };
|
|
@@ -22,3 +22,11 @@ module.exports = acpAdapter({
|
|
|
22
22
|
usage: false,
|
|
23
23
|
contextUsage: false,
|
|
24
24
|
});
|
|
25
|
+
// ~/.hermes/skills is Hermes' single source of truth (categories become subdirectories);
|
|
26
|
+
// it has no documented project-scope skills directory. HERMES_HOME relocates the home.
|
|
27
|
+
// https://hermes-agent.nousresearch.com/docs/user-guide/features/skills
|
|
28
|
+
module.exports.manifest.integrations.skills = {
|
|
29
|
+
global: ['.hermes/skills'],
|
|
30
|
+
project: [],
|
|
31
|
+
overrides: { '.hermes/skills': { env: 'HERMES_HOME', suffix: 'skills' } },
|
|
32
|
+
};
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
const { nativeAcp } = require('./native-acp');
|
|
2
|
-
module.exports = nativeAcp({ id: 'kiro-cli', name: 'Kiro', aliases: ['kiro', 'kiro-cli', 'kirocli'], args: ['acp', '--agent-engine', 'v3', '--auth-method', 'cli'] });
|
|
2
|
+
module.exports = nativeAcp({ id: 'kiro-cli', name: 'Kiro', aliases: ['kiro', 'kiro-cli', 'kirocli'], args: ['acp', '--agent-engine', 'v3', '--auth-method', 'cli'] });
|
|
3
|
+
module.exports.manifest.integrations.skills = { global: ['.kiro/skills'], project: ['.kiro/skills'] };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Native sessions own connections, tool discovery, authentication and approvals.
|
|
2
|
+
function nativeServers(managed = [], collaboration) {
|
|
3
|
+
return [...managed, ...(collaboration ? [{ name: 'harness-mix', ...collaboration }] : [])];
|
|
4
|
+
}
|
|
5
|
+
function acpServers(managed, collaboration) {
|
|
6
|
+
return nativeServers(managed, collaboration).map(s => {
|
|
7
|
+
if (s.url) {
|
|
8
|
+
return {
|
|
9
|
+
name: s.name,
|
|
10
|
+
url: s.url,
|
|
11
|
+
headers: Object.entries(s.http_headers || {}).map(([name, value]) => ({ name, value })),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
name: s.name,
|
|
16
|
+
command: s.command,
|
|
17
|
+
args: s.args || [],
|
|
18
|
+
env: Object.entries(s.env || {}).map(([name, value]) => ({ name, value })),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function namedServers(managed, collaboration) {
|
|
23
|
+
return Object.fromEntries(nativeServers(managed, collaboration).map(({ name, ...server }) => [name, server]));
|
|
24
|
+
}
|
|
25
|
+
module.exports = { nativeServers, acpServers, namedServers };
|
|
@@ -237,13 +237,12 @@ function nativeAcp({ id, name, args, aliases = [], command = argv => nativeComma
|
|
|
237
237
|
async describe() { return { models: null, thinkingLevels: [], permissionModes: [] }; },
|
|
238
238
|
async describeFor(s) { return catalog(s); },
|
|
239
239
|
async listModelsFor(s) { return catalog(s).models; },
|
|
240
|
-
async open({ thread, emit, diagnostic = () => {}, collaboration }) {
|
|
240
|
+
async open({ thread, emit, diagnostic = () => {}, collaboration, managedMcp = [] }) {
|
|
241
241
|
const s = { vendor: id, cwd: thread.cwd, nativeSessionId: thread.restore ? thread.nativeSessionId : null,
|
|
242
242
|
generation: 0, loading: true, active: false, closed: false, fault: null, emit, diagnostic,
|
|
243
243
|
environment: thread.environment || {}, tools: new Map(), charges: new Map(), confirmed: {},
|
|
244
244
|
state: { configOptions: [], models: null, modes: null, commands: [], usage: {} },
|
|
245
|
-
mcpServers:
|
|
246
|
-
env: Object.entries(collaboration.env).map(([name, value]) => ({ name, value })) }] : [],
|
|
245
|
+
mcpServers: require('./managed-mcp').acpServers(managedMcp, collaboration),
|
|
247
246
|
collaborationEnabled: !!collaboration,
|
|
248
247
|
};
|
|
249
248
|
s.interactions = new AcpInteractions(s, id);
|
|
@@ -430,6 +429,7 @@ function nativeAcp({ id, name, args, aliases = [], command = argv => nativeComma
|
|
|
430
429
|
};
|
|
431
430
|
return adapter;
|
|
432
431
|
}
|
|
433
|
-
|
|
432
|
+
manifest.integrations = { mcp: true };
|
|
433
|
+
return { manifest, create };
|
|
434
434
|
}
|
|
435
435
|
module.exports = { nativeAcp, catalog, project };
|
package/src/main/adapters/omp.js
CHANGED
|
@@ -10,3 +10,14 @@ module.exports = piFamily({
|
|
|
10
10
|
packageHint: 'npm i -g @oh-my-pi/pi-coding-agent 或 https://omp.sh/install',
|
|
11
11
|
aliases: ['omp', 'oh-my-pi'],
|
|
12
12
|
});
|
|
13
|
+
module.exports.manifest.integrations = {
|
|
14
|
+
mcp: false,
|
|
15
|
+
skills: {
|
|
16
|
+
global: ['.omp/agent/skills', '.agents/skills', '.pi/agent/skills'],
|
|
17
|
+
project: ['.omp/skills', '.agents/skills', '.pi/skills'],
|
|
18
|
+
overrides: {
|
|
19
|
+
'.omp/agent/skills': { env: 'OMP_CODING_AGENT_DIR', suffix: 'skills' },
|
|
20
|
+
'.pi/agent/skills': { env: 'PI_CODING_AGENT_DIR', suffix: 'skills' },
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -18,6 +18,12 @@ const manifest = {
|
|
|
18
18
|
capabilities: { streaming: true, thinking: false, tools: true, approvals: true, questions: false, models: true, thinkingLevels: true, permissionModes: false, resume: true, fork: false, forkFromMessage: false, compaction: false, usage: true, contextUsage: true, attachments: true },
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
manifest.integrations = { mcp: false, skills: {
|
|
22
|
+
global: ['.openclaw/skills', '.agents/skills'],
|
|
23
|
+
project: ['skills', '.agents/skills'],
|
|
24
|
+
overrides: { '.openclaw/skills': { env: 'OPENCLAW_STATE_DIR', suffix: 'skills' } },
|
|
25
|
+
} };
|
|
26
|
+
|
|
21
27
|
const textOfContent = (result) => (result?.content ?? []).map((c) => (c?.type === "text" ? c.text : "")).filter(Boolean).join("\n");
|
|
22
28
|
const stringifyArgs = (args) => {
|
|
23
29
|
if (args == null) return undefined;
|
|
@@ -54,9 +54,14 @@ function create() {
|
|
|
54
54
|
return new Promise(resolve => execFile(cli.command, cli.args, { windowsHide: true, timeout: 10000 }, (error, stdout) => resolve({ available: !error, detail: error ? 'OpenCode Server CLI unavailable' : `OpenCode ${stdout.trim()} · native HTTP/SSE` })));
|
|
55
55
|
},
|
|
56
56
|
async describe() { return { models: null, thinkingLevels: [], permissionModes: [] }; },
|
|
57
|
-
async
|
|
57
|
+
async inspectIntegrations(session) {
|
|
58
|
+
const rows = await session.host.request('GET', '/mcp', undefined, 5000);
|
|
59
|
+
return Object.entries(rows).map(([name, value]) => ({ name, status: value.status, tools: [] }));
|
|
60
|
+
},
|
|
61
|
+
async open({ thread, emit, collaboration, managedMcp = [] }) {
|
|
58
62
|
// L2 注入:OPENCODE_CONFIG_CONTENT 是最高优先级的运行时内联配置,合并而非覆盖用户配置,会话结束即失效,不污染任何配置文件
|
|
59
|
-
const
|
|
63
|
+
const servers = require('./managed-mcp').nativeServers(managedMcp, collaboration);
|
|
64
|
+
const overlay = servers.length ? { env: { OPENCODE_CONFIG_CONTENT: JSON.stringify({ mcp: Object.fromEntries(servers.map(s => [s.name, { type: 'local', command: [s.command, ...s.args], environment: s.env }])) }) } } : undefined;
|
|
60
65
|
const host = await new OpenCodeServer(thread.cwd, overlay).start();
|
|
61
66
|
try {
|
|
62
67
|
// Read the model catalog, never provider credentials or auth files.
|
|
@@ -175,4 +180,9 @@ function create() {
|
|
|
175
180
|
};
|
|
176
181
|
return adapter;
|
|
177
182
|
}
|
|
178
|
-
|
|
183
|
+
manifest.integrations = { mcp: true, skills: {
|
|
184
|
+
global: ['.config/opencode/skills', '.claude/skills', '.agents/skills'],
|
|
185
|
+
project: ['.opencode/skills', '.claude/skills', '.agents/skills'],
|
|
186
|
+
overrides: { '.config/opencode/skills': { env: 'XDG_CONFIG_HOME', suffix: 'opencode/skills' } },
|
|
187
|
+
} };
|
|
188
|
+
module.exports = { manifest, create, projectEvent, projectPart, describeSession };
|
package/src/main/adapters/pi.js
CHANGED
|
@@ -8,3 +8,8 @@ module.exports = piFamily({
|
|
|
8
8
|
bin: 'pi',
|
|
9
9
|
packageHint: 'npm i -g @earendil-works/pi-coding-agent',
|
|
10
10
|
});
|
|
11
|
+
module.exports.manifest.integrations = { mcp: false, skills: {
|
|
12
|
+
global: ['.pi/agent/skills', '.agents/skills'],
|
|
13
|
+
project: ['.pi/skills', '.agents/skills'],
|
|
14
|
+
overrides: { '.pi/agent/skills': { env: 'PI_CODING_AGENT_DIR', suffix: 'skills' } },
|
|
15
|
+
} };
|
|
@@ -5,3 +5,4 @@ module.exports = nativeAcp({
|
|
|
5
5
|
id: 'qoder', name: 'Qoder', args: ['--acp'],
|
|
6
6
|
capabilities: { questions: false, thinkingLevels: false, usage: true, contextUsage: true, attachments: true, fork: false, compaction: false },
|
|
7
7
|
});
|
|
8
|
+
module.exports.manifest.integrations.skills = { global: ['.qoder/skills'], project: ['.qoder/skills'] };
|
|
@@ -5,3 +5,7 @@ module.exports = nativeAcp({
|
|
|
5
5
|
id: 'trae', name: 'Trae', args: [],
|
|
6
6
|
capabilities: { questions: false, thinkingLevels: false, usage: true, contextUsage: true, attachments: false, fork: false, compaction: false },
|
|
7
7
|
});
|
|
8
|
+
// Trae IDE uses .trae/skills; TraeCode CLI uses .traecli/skills. Both are registered so the
|
|
9
|
+
// CLI-backed (ACP) session and the IDE share one managed root set.
|
|
10
|
+
// https://docs.trae.cn/cli_skills
|
|
11
|
+
module.exports.manifest.integrations.skills = { global: ['.trae/skills', '.traecli/skills'], project: ['.trae/skills', '.traecli/skills', '.agents/skills'] };
|
|
@@ -5,3 +5,6 @@ module.exports = nativeAcp({
|
|
|
5
5
|
id: 'zcode', name: 'ZCode', args: [],
|
|
6
6
|
capabilities: { questions: false, thinkingLevels: false, usage: true, contextUsage: true, attachments: false, fork: false, compaction: false },
|
|
7
7
|
});
|
|
8
|
+
// ZCode scans, per scope: .zcode/skills then .agents/skills (deeper workspace levels win).
|
|
9
|
+
// https://zcode.z.ai/en/docs/skill
|
|
10
|
+
module.exports.manifest.integrations.skills = { global: ['.zcode/skills', '.agents/skills'], project: ['.zcode/skills', '.agents/skills'] };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const { CAPABILITY_GROUPS, createCapabilities } = require('../shared-contracts');
|
|
2
3
|
|
|
3
4
|
// Adapter Manifest 校验与能力归一化(§22)。
|
|
4
5
|
// 现有 Adapter 使用扁平 capability flags;Core/UI 消费分组结构(§22)。
|
|
@@ -16,7 +17,21 @@ function validateManifest(manifest) {
|
|
|
16
17
|
if (!manifest || typeof manifest !== 'object') return ['manifest must be an object'];
|
|
17
18
|
if (typeof manifest.id !== 'string' || !manifest.id) errors.push('manifest.id is required');
|
|
18
19
|
if (typeof manifest.name !== 'string' || !manifest.name) errors.push('manifest.name is required');
|
|
19
|
-
if (manifest.icon != null && typeof manifest.icon !== 'string') errors.push('manifest.icon must be a string');
|
|
20
|
+
if (manifest.icon != null && typeof manifest.icon !== 'string') errors.push('manifest.icon must be a string');
|
|
21
|
+
if (manifest.integrations != null) {
|
|
22
|
+
const integrations = manifest.integrations;
|
|
23
|
+
if (!integrations || typeof integrations !== 'object' || Array.isArray(integrations)) errors.push('manifest.integrations must be an object');
|
|
24
|
+
else {
|
|
25
|
+
if (typeof integrations.mcp !== 'boolean') errors.push('manifest.integrations.mcp must be a boolean');
|
|
26
|
+
if (integrations.skills != null) {
|
|
27
|
+
const skills = integrations.skills;
|
|
28
|
+
if (!skills || typeof skills !== 'object' || Array.isArray(skills)) errors.push('manifest.integrations.skills must be an object');
|
|
29
|
+
else for (const scope of ['global', 'project']) {
|
|
30
|
+
if (!Array.isArray(skills[scope]) || skills[scope].some(value => typeof value !== 'string' || path.isAbsolute(value) || value.includes('..'))) errors.push(`manifest.integrations.skills.${scope} must contain safe relative paths`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
20
35
|
const caps = manifest.capabilities;
|
|
21
36
|
if (!caps || typeof caps !== 'object' || Array.isArray(caps)) {
|
|
22
37
|
errors.push('manifest.capabilities must be an object');
|