@openagents-org/agent-launcher 0.2.167 → 0.2.169
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/icons/deepseek.svg +1 -0
- package/package.json +1 -1
- package/registry.json +89 -0
- package/src/adapters/deepseek-runtime.js +634 -0
- package/src/adapters/deepseek.js +1190 -0
- package/src/adapters/index.js +4 -1
- package/src/adapters/utils.js +24 -4
- package/src/adapters/workspace-prompt.js +63 -0
- package/src/installer.js +60 -10
- package/src/workspace-client.js +36 -1
package/src/adapters/index.js
CHANGED
|
@@ -21,6 +21,7 @@ const ClineAdapter = require('./cline');
|
|
|
21
21
|
const AmpAdapter = require('./amp');
|
|
22
22
|
const MiniSweAgentAdapter = require('./mini');
|
|
23
23
|
const PiAdapter = require('./pi');
|
|
24
|
+
const DeepSeekAdapter = require('./deepseek');
|
|
24
25
|
|
|
25
26
|
const ADAPTER_MAP = {
|
|
26
27
|
openclaw: OpenClawAdapter,
|
|
@@ -39,11 +40,12 @@ const ADAPTER_MAP = {
|
|
|
39
40
|
amp: AmpAdapter,
|
|
40
41
|
'mini-swe-agent': MiniSweAgentAdapter,
|
|
41
42
|
pi: PiAdapter,
|
|
43
|
+
deepseek: DeepSeekAdapter,
|
|
42
44
|
};
|
|
43
45
|
|
|
44
46
|
/**
|
|
45
47
|
* Create an adapter instance for the given agent type.
|
|
46
|
-
* @param {string} type - Agent type (openclaw, claude, codex, opencode, nanoclaw, cursor, hermes, gemini, kimi, aider, goose, copilot, cline, amp, mini-swe-agent, pi)
|
|
48
|
+
* @param {string} type - Agent type (openclaw, claude, codex, opencode, nanoclaw, cursor, hermes, gemini, kimi, aider, goose, copilot, cline, amp, mini-swe-agent, pi, deepseek)
|
|
47
49
|
* @param {object} opts - Adapter constructor options
|
|
48
50
|
* @returns {BaseAdapter}
|
|
49
51
|
*/
|
|
@@ -73,6 +75,7 @@ module.exports = {
|
|
|
73
75
|
AmpAdapter,
|
|
74
76
|
MiniSweAgentAdapter,
|
|
75
77
|
PiAdapter,
|
|
78
|
+
DeepSeekAdapter,
|
|
76
79
|
createAdapter,
|
|
77
80
|
ADAPTER_MAP,
|
|
78
81
|
};
|
package/src/adapters/utils.js
CHANGED
|
@@ -52,11 +52,30 @@ function generateSessionTitle(message, maxWords = 6) {
|
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* Format attachment metadata into text to append to an agent prompt.
|
|
55
|
+
*
|
|
56
|
+
* `tokenExpr` is the SHELL EXPRESSION the agent should use for the workspace
|
|
57
|
+
* token, not the token itself. It defaults to `$TOKEN` for the adapters that
|
|
58
|
+
* already export the token under that name. An adapter whose prompt must never
|
|
59
|
+
* contain the literal token (deepseek writes its prompt to a file and exports
|
|
60
|
+
* OPENAGENTS_WORKSPACE_TOKEN into the child environment) passes its own
|
|
61
|
+
* expression instead — otherwise `$TOKEN` expands to the empty string in the
|
|
62
|
+
* child and every attachment download silently 401s.
|
|
63
|
+
*
|
|
55
64
|
* @param {Array} attachments
|
|
56
65
|
* @param {'mcp'|'skills'} [toolMode='mcp']
|
|
57
66
|
* @param {boolean} [isWindows]
|
|
67
|
+
* @param {object} [opts]
|
|
68
|
+
* @param {string} [opts.tokenExpr='$TOKEN'] shell expression yielding the token
|
|
69
|
+
* @param {string} [opts.endpoint] base URL used when an attachment carries no
|
|
70
|
+
* absolute `url`. Without it the fallback emits a literal `{WORKSPACE_API}`
|
|
71
|
+
* placeholder that nothing substitutes.
|
|
58
72
|
*/
|
|
59
|
-
function formatAttachmentsForPrompt(
|
|
73
|
+
function formatAttachmentsForPrompt(
|
|
74
|
+
attachments,
|
|
75
|
+
toolMode = 'mcp',
|
|
76
|
+
isWindows = process.platform === 'win32',
|
|
77
|
+
{ tokenExpr = '$TOKEN', endpoint = null } = {},
|
|
78
|
+
) {
|
|
60
79
|
if (!attachments || attachments.length === 0) return null;
|
|
61
80
|
|
|
62
81
|
const lines = ['\n[Attached files]'];
|
|
@@ -65,21 +84,22 @@ function formatAttachmentsForPrompt(attachments, toolMode = 'mcp', isWindows = p
|
|
|
65
84
|
const fileId = att.fileId || '';
|
|
66
85
|
const contentType = att.contentType || '';
|
|
67
86
|
if (toolMode === 'skills') {
|
|
68
|
-
const
|
|
87
|
+
const base = endpoint ? String(endpoint).replace(/\/+$/, '') : '{WORKSPACE_API}';
|
|
88
|
+
const url = att.url || `${base}/v1/files/${fileId}`;
|
|
69
89
|
const curl = isWindows ? 'curl.exe' : 'curl';
|
|
70
90
|
const tmpDir = isWindows ? '$env:TEMP' : '/tmp';
|
|
71
91
|
if (contentType.startsWith('image/')) {
|
|
72
92
|
lines.push(
|
|
73
93
|
`- Image: ${filename} (file_id: ${fileId}) — ` +
|
|
74
94
|
`download with curl, then use your Read tool on the local file to view it:\n` +
|
|
75
|
-
` Step 1: ${curl} -s -H "X-Workspace-Token: $
|
|
95
|
+
` Step 1: ${curl} -s -H "X-Workspace-Token: ${tokenExpr}" "${url}" -o ${tmpDir}/${filename}\n` +
|
|
76
96
|
` Step 2: Use the Read tool on ${tmpDir}/${filename} to see the image`
|
|
77
97
|
);
|
|
78
98
|
} else {
|
|
79
99
|
lines.push(
|
|
80
100
|
`- File: ${filename} (file_id: ${fileId}, type: ${contentType}) — ` +
|
|
81
101
|
`download with curl, then use your Read tool on the local file:\n` +
|
|
82
|
-
` Step 1: ${curl} -s -H "X-Workspace-Token: $
|
|
102
|
+
` Step 1: ${curl} -s -H "X-Workspace-Token: ${tokenExpr}" "${url}" -o ${tmpDir}/${filename}\n` +
|
|
83
103
|
` Step 2: Use the Read tool on ${tmpDir}/${filename} to read the file`
|
|
84
104
|
);
|
|
85
105
|
}
|
|
@@ -1021,6 +1021,68 @@ function buildPiSystemPrompt({ agentName, workspaceId, channelName, endpoint, to
|
|
|
1021
1021
|
return identity + directive + '\n' + collab + '\n' + modePrompt + '\n' + api + '\n' + buildGuardrails();
|
|
1022
1022
|
}
|
|
1023
1023
|
|
|
1024
|
+
/**
|
|
1025
|
+
* Build the task FILE contents for the DeepSeek Harness adapter.
|
|
1026
|
+
*
|
|
1027
|
+
* dsh takes its task as a positional argv element and has no stdin channel, so
|
|
1028
|
+
* the adapter writes everything here to a private file and passes only a
|
|
1029
|
+
* constant sentence on the command line. Two consequences shape this function:
|
|
1030
|
+
*
|
|
1031
|
+
* - The workspace token must NEVER appear. `tokenExpr` is a SHELL EXPRESSION
|
|
1032
|
+
* (`$OPENAGENTS_WORKSPACE_TOKEN`, or `$env:OPENAGENTS_WORKSPACE_TOKEN` on
|
|
1033
|
+
* Windows) that the agent expands at run time from the child environment.
|
|
1034
|
+
* Passing the real token here would put it on disk for no benefit — and the
|
|
1035
|
+
* guard below makes that a loud failure rather than a silent leak.
|
|
1036
|
+
*
|
|
1037
|
+
* - dsh has no session resume: each run is a fresh session. The channel recap
|
|
1038
|
+
* is therefore part of the task itself rather than something the agent can
|
|
1039
|
+
* be expected to remember.
|
|
1040
|
+
*/
|
|
1041
|
+
function buildDeepSeekTaskFile({
|
|
1042
|
+
agentName, workspaceId, channelName, endpoint, tokenExpr,
|
|
1043
|
+
mode = 'execute', disabledModules, browserEnabled = false,
|
|
1044
|
+
recap = '', request = '', attachments = '',
|
|
1045
|
+
}) {
|
|
1046
|
+
// A token expression is a shell reference, never a credential. Anything that
|
|
1047
|
+
// does not start with `$` is almost certainly a real token passed by mistake.
|
|
1048
|
+
if (!tokenExpr || !/^\$/.test(String(tokenExpr))) {
|
|
1049
|
+
throw new Error(
|
|
1050
|
+
'buildDeepSeekTaskFile: tokenExpr must be a shell expression such as '
|
|
1051
|
+
+ '$OPENAGENTS_WORKSPACE_TOKEN — never the workspace token itself.',
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
const identity = buildWorkspaceIdentity(agentName, workspaceId, channelName, mode, 'skills');
|
|
1056
|
+
const directive = buildBrowserDirective(browserEnabled);
|
|
1057
|
+
const collab = buildCollaborationPrompt('skills', workspaceSkillName(agentName));
|
|
1058
|
+
const modePrompt = buildModePrompt(mode);
|
|
1059
|
+
const api = buildApiSkillsPrompt({
|
|
1060
|
+
endpoint, workspaceId, token: tokenExpr, agentName, channelName, disabledModules, mode,
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
const parts = [identity, directive, '\n', collab, '\n', modePrompt, '\n', api, '\n', buildGuardrails()];
|
|
1064
|
+
|
|
1065
|
+
if (recap) {
|
|
1066
|
+
parts.push(
|
|
1067
|
+
'\n## Recent channel history\n'
|
|
1068
|
+
+ 'You are running as a fresh process with no memory of earlier turns. '
|
|
1069
|
+
+ 'This is the relevant history — treat it as context, not as new work.\n\n'
|
|
1070
|
+
+ recap + '\n',
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
parts.push(
|
|
1075
|
+
'\n## Your task\n'
|
|
1076
|
+
+ 'Respond to the request below. Everything above is context and standing '
|
|
1077
|
+
+ 'instruction; only this section is what you were asked to do now.\n\n'
|
|
1078
|
+
+ String(request || '').trim() + '\n',
|
|
1079
|
+
);
|
|
1080
|
+
|
|
1081
|
+
if (attachments) parts.push('\n' + attachments + '\n');
|
|
1082
|
+
|
|
1083
|
+
return parts.join('');
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1024
1086
|
/**
|
|
1025
1087
|
* Build workspace skill markdown for OpenCode (written to .opencode/skills/).
|
|
1026
1088
|
*/
|
|
@@ -1127,6 +1189,7 @@ module.exports = {
|
|
|
1127
1189
|
buildOpenCodeSystemPrompt,
|
|
1128
1190
|
buildOpenCodeSkillMd,
|
|
1129
1191
|
buildPiSystemPrompt,
|
|
1192
|
+
buildDeepSeekTaskFile,
|
|
1130
1193
|
buildClaudeSkillMd,
|
|
1131
1194
|
buildCursorSkillMd,
|
|
1132
1195
|
};
|
package/src/installer.js
CHANGED
|
@@ -609,7 +609,12 @@ class Installer {
|
|
|
609
609
|
env: getEnhancedEnv(),
|
|
610
610
|
timeout: 10000,
|
|
611
611
|
}).trim();
|
|
612
|
-
|
|
612
|
+
// The optional `-prerelease` tail is NOT decoration. An agent pinned to a
|
|
613
|
+
// preview release (`@deepseek-ai/dsh@0.1.0-rc.6`) is gated on the exact
|
|
614
|
+
// version, and dropping the suffix reported a correctly installed rc.6 as
|
|
615
|
+
// plain `0.1.0` — which then failed its own gate. Agents without a
|
|
616
|
+
// prerelease segment match exactly as before.
|
|
617
|
+
const match = raw.match(/(\d+[\d.]+\d+(?:-[0-9A-Za-z.-]+)?)/);
|
|
613
618
|
version = match ? match[1] : (raw.split('\n')[0] || null);
|
|
614
619
|
} catch {}
|
|
615
620
|
|
|
@@ -618,14 +623,52 @@ class Installer {
|
|
|
618
623
|
}
|
|
619
624
|
|
|
620
625
|
/**
|
|
621
|
-
* Evaluate
|
|
626
|
+
* Evaluate a registry version gate against a detected version. Generic for any
|
|
622
627
|
* agent. Returns { compatible: true|false|null, minVersion, message }.
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
628
|
+
*
|
|
629
|
+
* Two gate shapes, in priority order:
|
|
630
|
+
*
|
|
631
|
+
* `install.supported_version` — EXACT match. For agents pinned to an
|
|
632
|
+
* upstream preview whose configuration can change between releases, a floor
|
|
633
|
+
* is the wrong shape: it happily admits the next preview, which is precisely
|
|
634
|
+
* the version the adapter has not been written against. Declared by
|
|
635
|
+
* deepseek (dsh is a developer preview).
|
|
636
|
+
*
|
|
637
|
+
* `install.min_version` / `check_ready.min_version` — a floor (>=). The
|
|
638
|
+
* original behaviour, unchanged for every agent that does not declare
|
|
639
|
+
* supported_version.
|
|
627
640
|
*/
|
|
628
641
|
_evaluateCompatibility(entry, version) {
|
|
642
|
+
const supported = (entry && entry.install && entry.install.supported_version) || null;
|
|
643
|
+
if (supported) {
|
|
644
|
+
// Compare the version-shaped token from each side, prerelease included.
|
|
645
|
+
// `compareVersions` cannot be used: it strips `-rc.N`, so rc.5 and rc.6
|
|
646
|
+
// would compare equal and the exact gate would admit the wrong preview.
|
|
647
|
+
const norm = (v) => {
|
|
648
|
+
const m = String(v == null ? '' : v).match(/\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/);
|
|
649
|
+
return m ? m[0] : null;
|
|
650
|
+
};
|
|
651
|
+
const got = norm(version);
|
|
652
|
+
const want = norm(supported);
|
|
653
|
+
if (!got) {
|
|
654
|
+
return {
|
|
655
|
+
compatible: null,
|
|
656
|
+
minVersion: supported,
|
|
657
|
+
message: `Version unknown (requires exactly ${supported})`,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
if (got !== want) {
|
|
661
|
+
return {
|
|
662
|
+
compatible: false,
|
|
663
|
+
minVersion: supported,
|
|
664
|
+
message:
|
|
665
|
+
`${(entry.label || entry.name || 'This agent')} ${got} is not supported — ` +
|
|
666
|
+
`it is pinned to ${want} exactly (upstream ships it as a developer preview).`,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
return { compatible: true, minVersion: supported, message: null };
|
|
670
|
+
}
|
|
671
|
+
|
|
629
672
|
// Opt-in floor declared under either install.min_version or
|
|
630
673
|
// check_ready.min_version (e.g. cline pins check_ready.min_version).
|
|
631
674
|
const minVersion = (entry && (
|
|
@@ -1326,11 +1369,18 @@ class Installer {
|
|
|
1326
1369
|
// npm install -g <pkg> → npm uninstall --prefix <runtimeDir> <pkg>
|
|
1327
1370
|
if (installCmd.includes('npm install')) {
|
|
1328
1371
|
const prefixDir = agentType ? getRuntimePrefix(agentType) : path.join(os.homedir(), '.openagents', 'nodejs');
|
|
1329
|
-
|
|
1372
|
+
const cmd = installCmd
|
|
1330
1373
|
.replace('npm install -g', `npm uninstall --prefix "${prefixDir}"`)
|
|
1331
|
-
.replace('npm install', 'npm uninstall')
|
|
1332
|
-
|
|
1333
|
-
|
|
1374
|
+
.replace('npm install', 'npm uninstall');
|
|
1375
|
+
// Drop the version spec, whatever shape it takes. This used to be two
|
|
1376
|
+
// blind global replaces — `@latest` and `@[\d.]+` — and the second one
|
|
1377
|
+
// understood only digits and dots, so a PRERELEASE pin was cut in half:
|
|
1378
|
+
// @deepseek-ai/dsh@0.1.0-rc.6 → @deepseek-ai/dsh-rc.6
|
|
1379
|
+
// npm exits 0 when asked to remove a package that is not installed, so
|
|
1380
|
+
// the uninstall reported success, removed nothing, and left the agent
|
|
1381
|
+
// looking installed forever. Anchoring to the package token at the end of
|
|
1382
|
+
// the command makes the shape of the version irrelevant.
|
|
1383
|
+
return cmd.replace(/(\s@?[\w-]+(?:\/[\w-]+)?)@\S+\s*$/, '$1');
|
|
1334
1384
|
}
|
|
1335
1385
|
|
|
1336
1386
|
// pip install <pkg> → pip uninstall -y <pkg>
|
package/src/workspace-client.js
CHANGED
|
@@ -209,6 +209,27 @@ class WorkspaceClient {
|
|
|
209
209
|
const payload = { content, message_type: messageType };
|
|
210
210
|
if (attachments && attachments.length) payload.attachments = attachments;
|
|
211
211
|
|
|
212
|
+
// DM sessions (`dm:<counterpart address>`, created by _eventToMessage for
|
|
213
|
+
// incoming direct messages) reply as direct events to the counterpart —
|
|
214
|
+
// not into a `channel/dm:…` channel, which no client would ever read.
|
|
215
|
+
if (channelName && channelName.startsWith('dm:')) {
|
|
216
|
+
const counterpart = channelName.slice(3);
|
|
217
|
+
const meta = { ...(metadata || {}) };
|
|
218
|
+
if (!meta.target_agents) {
|
|
219
|
+
meta.target_agents = counterpart.startsWith('openagents:')
|
|
220
|
+
? [counterpart.replace(/^openagents:/, '')]
|
|
221
|
+
: ['__no_response__'];
|
|
222
|
+
}
|
|
223
|
+
return this.sendEvent(workspaceId, {
|
|
224
|
+
type: 'workspace.message.posted',
|
|
225
|
+
source,
|
|
226
|
+
target: counterpart,
|
|
227
|
+
payload,
|
|
228
|
+
metadata: meta,
|
|
229
|
+
visibility: 'direct',
|
|
230
|
+
}, token, sessionId);
|
|
231
|
+
}
|
|
232
|
+
|
|
212
233
|
return this.sendEvent(workspaceId, {
|
|
213
234
|
type: 'workspace.message.posted',
|
|
214
235
|
source,
|
|
@@ -841,9 +862,23 @@ class WorkspaceClient {
|
|
|
841
862
|
const target = event.target || '';
|
|
842
863
|
const ts = event.timestamp;
|
|
843
864
|
|
|
865
|
+
// Session id: channels map to their name. A direct message (visibility
|
|
866
|
+
// 'direct', target = this agent's own address) maps to a per-counterpart
|
|
867
|
+
// DM session `dm:<source address>` — so the adapter keeps one CLI session
|
|
868
|
+
// per DM peer, and sendMessage() below routes replies back as direct
|
|
869
|
+
// events to that peer instead of into a channel.
|
|
870
|
+
let sessionId;
|
|
871
|
+
if (target.startsWith('channel/')) {
|
|
872
|
+
sessionId = target.replace('channel/', '');
|
|
873
|
+
} else if (event.visibility === 'direct' && source) {
|
|
874
|
+
sessionId = `dm:${source}`;
|
|
875
|
+
} else {
|
|
876
|
+
sessionId = target;
|
|
877
|
+
}
|
|
878
|
+
|
|
844
879
|
const msg = {
|
|
845
880
|
messageId: event.id || '',
|
|
846
|
-
sessionId
|
|
881
|
+
sessionId,
|
|
847
882
|
senderType: isHuman ? 'human' : 'agent',
|
|
848
883
|
senderName,
|
|
849
884
|
content: (payload.content || event.content || ''),
|