@quonaro/dsh-plugin-devin 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 quonaro
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,80 @@
1
- # Temporary Holding Version
1
+ # dsh-plugin-devin
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Unofficial bridge between the [Devin CLI](https://devin.ai) and DeepSeek Harness: a `devin` delegation tool plus a `/devin` command running `devin -p` (headless print mode), and a `devin` LLM provider route with automatic model discovery — all over the `ctx.subprocess` seam. Not affiliated with Cognition.
4
+
5
+ ## What the agent gets
6
+
7
+ - **`devin` tool** — spawns `devin -p "<prompt>"` as a managed child process. Per-call `cwd`, `model`, `permissionMode`, `resume`, `cloud`, and `timeoutMs`. Returns `{ ok, exitCode, stdout, stderr, timedOut, truncated }`.
8
+ - **`/devin <task>`** — slash command shortcut (when the profile composes the commands service).
9
+
10
+ Cancelling the tool call aborts the child via the subprocess seam's terminate escalation (SIGTERM → grace → SIGKILL on the process tree); output capture is bounded and keeps the tail on overflow.
11
+
12
+ ## Prerequisites
13
+
14
+ - `devin` CLI on `PATH` (or set `devinPath` to an absolute path)
15
+ - `devin auth login` completed, or `WINDSURF_API_KEY` exported (forwarded to the child via `forwardEnv`)
16
+
17
+ ## Install
18
+
19
+ ```sh
20
+ # from npm
21
+ dsh plugin --profile <your-profile> add @quonaro/dsh-plugin-devin
22
+
23
+ # or straight from a git host
24
+ dsh plugin --profile <your-profile> add github:quonaro/dsh-plugin-devin
25
+
26
+ # or from a local checkout
27
+ dsh plugin --profile <your-profile> add /path/to/dsh-plugin-devin
28
+ ```
29
+
30
+ The package declares `dsh.bundle`, so `dsh plugin add` applies `cordis.patch.yml` to the profile automatically. On pnpm ≥ 10 the first git install refuses the `prepare` build — copy the package key pnpm prints into the profile's `pnpm-workspace.yaml` `allowBuilds:` and re-run.
31
+
32
+ ## Configuration
33
+
34
+ Edit the `dsh-plugin-devin` row in the profile's `cordis.patch.yml`, or the derived settings namespace in the GUI. All fields are volatile — changes apply to the next call without a restart.
35
+
36
+ | Key | Default | Notes |
37
+ | --- | --- | --- |
38
+ | `devinPath` | `devin` | Executable name or absolute path |
39
+ | `permissionMode` | `accept-edits` | Headless runs can't answer prompts; `bypass`/`autonomous` for fully unattended work |
40
+ | `model` | `''` | Devin model; empty = CLI/account default |
41
+ | `cloud` | `false` | `true` → `devin --cloud` (Devin Cloud sessions) |
42
+ | `timeoutMs` | `600000` | Cooperative per-call timeout |
43
+ | `maxOutputBytes` | `1048576` | In-memory cap per captured stream; tail kept on overflow |
44
+ | `respectWorkspaceTrust` | `false` | `false` → passes `--respect-workspace-trust false` (required for `-p` in untrusted dirs) |
45
+ | `forwardEnv` | PATH/HOME/USER/XDG_*/WINDSURF_API_KEY | Env var names forwarded to the child on top of the scrubbed base |
46
+ | `extraArgs` | `[]` | Extra flags appended verbatim, e.g. `['--sandbox']` |
47
+
48
+ ## Develop
49
+
50
+ ```sh
51
+ pnpm install
52
+ pnpm test # typecheck + tsdown build + smoke test (uses a fake devin binary)
53
+ ```
54
+
55
+ ## Notes
56
+
57
+ - The plugin hard-injects `tools` and `subprocess` — profiles must compose a subprocess provider (all shipped base profiles do; `sdk-minimal` does not).
58
+ - `--print` mode fails on the workspace-trust prompt in untrusted directories; that's why `respectWorkspaceTrust: false` is the default. Set it `true` if you prefer Devin's own trust flow.
59
+ - An alternative shape — a persistent `devin acp` bridge with resumable sessions — is deliberately out of scope for v0.1; `resume` still lets you chain `-p` calls by session ID.
60
+
61
+ ## Provider half (`@quonaro/dsh-plugin-devin/provider`)
62
+
63
+ The package also ships an LLM-adapter entry point that registers `devin` as a **provider route** — it shows up in Settings → Models and `/model`, and the whole dsh agent loop can run on it.
64
+
65
+ Each generation spawns `devin -p` with the flattened conversation transcript and streams stdout back as one text block. `listModels` probes `devin acp` (ACP `initialize` + `session/new`) for the account's real model catalog and falls back to the static `models` table (`{id, devinModel, name}`) on failure; the session-title aux call is answered locally when `localSessionTitles` is on.
66
+
67
+ **Semantics:** Devin is an agent, not a model. Every model call is a complete headless Devin session running its own tool loop — per-response latency is minutes, and harness-side tools are never invoked. The `devin` *tool* (main entry) is the right shape for delegation; the provider exists for routing whole conversations through Devin.
68
+
69
+ | Extra config | Default | Notes |
70
+ | --- | --- | --- |
71
+ | `cwd` | `''` | Working dir for spawned sessions; empty = harness cwd |
72
+ | `timeoutMs` | `1800000` | Provider calls get a longer budget than tool calls |
73
+ | `models` | `default`, `opus` | Advertised model ids → `--model` value + display name |
74
+ | `brief` | (English wrapper) | Instruction prepended to each flattened transcript |
75
+ | `localSessionTitles` | `true` | Don't spend a Devin run on session-title generation |
76
+ | `autoDiscoverModels` | `true` | Probe `devin acp` (`initialize` + `session/new`) for the account's real model catalog — the same mechanism ACP clients like Zed use. Falls back to the static `models` table on failure |
77
+ | `discoveryTimeoutMs` | `60000` | Timeout for one discovery probe |
78
+ | `discoveryCacheMs` | `300000` | Cache a successful probe before re-probing |
79
+
80
+ Both halves are independent rows in `cordis.patch.yml` — remove `dsh-plugin-devin-provider` to keep only the tool, or `dsh-plugin-devin` to keep only the provider.
@@ -0,0 +1,72 @@
1
+ # Bundle config layer for dsh-plugin-devin. Applied to a profile by `dsh plugin add`.
2
+ #
3
+ # The row `id` is also the settings namespace the loader derives for volatile
4
+ # Config fields, so it must match `name` in package.json and src/index.ts.
5
+ - insert:
6
+ - id: dsh-plugin-devin
7
+ name: '@quonaro/dsh-plugin-devin'
8
+ config:
9
+ # Path or PATH-resolved name of the Devin CLI executable.
10
+ devinPath: 'devin'
11
+ # Permission mode passed as `--permission-mode`. Headless `devin -p`
12
+ # cannot answer prompts, so pick a mode that does not stall:
13
+ # accept-edits auto-approves file edits, bypass auto-approves everything.
14
+ permissionMode: 'accept-edits'
15
+ # Default model for spawned sessions; empty means the CLI/account default.
16
+ model: ''
17
+ # Pass --cloud to drive Devin Cloud sessions instead of the local agent.
18
+ cloud: false
19
+ # Cooperative timeout for one delegation call.
20
+ timeoutMs: 600000
21
+ # In-memory cap per output stream; overflow keeps the tail.
22
+ maxOutputBytes: 1048576
23
+ # When false (default) the plugin passes `--respect-workspace-trust false`
24
+ # so non-interactive runs do not fail in untrusted directories.
25
+ respectWorkspaceTrust: false
26
+ # Environment variable names forwarded to the devin child process on top
27
+ # of the subprocess seam's scrubbed base. Devin needs HOME/XDG paths to
28
+ # find stored credentials; WINDSURF_API_KEY is forwarded when present.
29
+ forwardEnv:
30
+ - PATH
31
+ - HOME
32
+ - USER
33
+ - XDG_CONFIG_HOME
34
+ - XDG_DATA_HOME
35
+ - WINDSURF_API_KEY
36
+ # Extra CLI flags appended verbatim before the prompt, e.g. ['--sandbox'].
37
+ extraArgs: []
38
+
39
+ # Provider half: registers `devin` as an LLM provider route (Settings → Models).
40
+ # Remove this row if you only want the delegation tool.
41
+ - id: dsh-plugin-devin-provider
42
+ name: '@quonaro/dsh-plugin-devin/provider'
43
+ config:
44
+ devinPath: 'devin'
45
+ permissionMode: 'accept-edits'
46
+ cloud: false
47
+ cwd: ''
48
+ timeoutMs: 1800000
49
+ stderrMaxBytes: 65536
50
+ respectWorkspaceTrust: false
51
+ forwardEnv:
52
+ - PATH
53
+ - HOME
54
+ - USER
55
+ - XDG_CONFIG_HOME
56
+ - XDG_DATA_HOME
57
+ - WINDSURF_API_KEY
58
+ extraArgs: []
59
+ models:
60
+ - id: default
61
+ devinModel: ''
62
+ name: 'Devin (account default)'
63
+ - id: opus
64
+ devinModel: 'opus'
65
+ name: 'Devin Opus'
66
+ localSessionTitles: true
67
+ # Probe `devin acp` (initialize + session/new) for the account's real
68
+ # model catalog; falls back to the static `models` table on failure.
69
+ autoDiscoverModels: true
70
+ discoveryTimeoutMs: 60000
71
+ # Cache a successful probe this long before re-probing.
72
+ discoveryCacheMs: 300000
package/lib/index.d.ts ADDED
@@ -0,0 +1,79 @@
1
+ import Schema from "@deepseek-ai/schemastery";
2
+ import { Context, Volatile } from "@deepseek-ai/cordis";
3
+ //#region src/index.d.ts
4
+ /** Host plugin name; must match package.json `name` and the cordis.patch.yml row id. */
5
+ declare const name = "@quonaro/dsh-plugin-devin";
6
+ /** Services this plugin needs before it loads: the tool registry and a subprocess provider. */
7
+ declare const inject: string[];
8
+ /** Plugin configuration; every field is a Volatile re-read at each call. */
9
+ interface Config {
10
+ /** Path or PATH-resolved name of the Devin CLI executable. */
11
+ devinPath: Volatile<string>;
12
+ /** `--permission-mode` for spawned sessions; headless runs cannot answer prompts. */
13
+ permissionMode: Volatile<string>;
14
+ /** Default `--model`; empty string means the CLI/account default. */
15
+ model: Volatile<string>;
16
+ /** Spawn with `--cloud` to drive Devin Cloud sessions instead of the local agent. */
17
+ cloud: Volatile<boolean>;
18
+ /** Cooperative timeout in milliseconds for one delegation call. */
19
+ timeoutMs: Volatile<number>;
20
+ /** In-memory cap per captured stream; overflow keeps the tail. */
21
+ maxOutputBytes: Volatile<number>;
22
+ /** When false, `--respect-workspace-trust false` is passed (non-interactive runs cannot show the trust prompt). */
23
+ respectWorkspaceTrust: Volatile<boolean>;
24
+ /** Environment variable names forwarded to the devin child process. */
25
+ forwardEnv: Volatile<string[]>;
26
+ /** Extra CLI flags appended verbatim before the prompt. */
27
+ extraArgs: Volatile<string[]>;
28
+ }
29
+ /** Schemastery schema: defaults live here; cordis.yml and GUI edits are validated against it. */
30
+ declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
31
+ devinPath: Schema<string, string, "volatile-defined">;
32
+ permissionMode: Schema<"normal" | "accept-edits" | "smart" | "bypass" | "autonomous", "normal" | "accept-edits" | "smart" | "bypass" | "autonomous", "volatile-defined">;
33
+ model: Schema<string, string, "volatile-defined">;
34
+ cloud: Schema<boolean, boolean, "volatile-defined">;
35
+ timeoutMs: Schema<number, number, "volatile-defined">;
36
+ maxOutputBytes: Schema<number, number, "volatile-defined">;
37
+ respectWorkspaceTrust: Schema<boolean, boolean, "volatile-defined">;
38
+ forwardEnv: Schema<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
39
+ extraArgs: Schema<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
40
+ }>>, Schemastery.ObjectT<NoInfer<{
41
+ devinPath: Schema<string, string, "volatile-defined">;
42
+ permissionMode: Schema<"normal" | "accept-edits" | "smart" | "bypass" | "autonomous", "normal" | "accept-edits" | "smart" | "bypass" | "autonomous", "volatile-defined">;
43
+ model: Schema<string, string, "volatile-defined">;
44
+ cloud: Schema<boolean, boolean, "volatile-defined">;
45
+ timeoutMs: Schema<number, number, "volatile-defined">;
46
+ maxOutputBytes: Schema<number, number, "volatile-defined">;
47
+ respectWorkspaceTrust: Schema<boolean, boolean, "volatile-defined">;
48
+ forwardEnv: Schema<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
49
+ extraArgs: Schema<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
50
+ }>>, "plain">;
51
+ /** /devin command payload shape (minimal subset of dsh-commands CommandInvocation). */
52
+ interface DevinCommandInvocation {
53
+ readonly rawInput: string;
54
+ }
55
+ interface DevinCommandResult {
56
+ kind: 'success' | 'error';
57
+ text?: string;
58
+ }
59
+ interface DevinCommandsLike {
60
+ register(definition: {
61
+ readonly name: string;
62
+ readonly description: string;
63
+ readonly handler: (invocation: DevinCommandInvocation) => DevinCommandResult | Promise<DevinCommandResult>;
64
+ }): unknown;
65
+ }
66
+ declare module '@deepseek-ai/cordis' {
67
+ interface Context {
68
+ /** Command registry (provided at runtime by @deepseek-ai/dsh-commands, optional). */
69
+ commands: DevinCommandsLike;
70
+ }
71
+ }
72
+ /**
73
+ * Plugin body: register the `devin` tool, and `/devin` when a commands
74
+ * service is composed. All registrations are effects — unloading the plugin
75
+ * reverts them automatically.
76
+ */
77
+ declare function apply(ctx: Context, config: Config): void;
78
+ //#endregion
79
+ export { Config, apply, inject, name };
package/lib/index.js ADDED
@@ -0,0 +1,209 @@
1
+ import { n as forwardedEnv, t as devinPrintArgv } from "./shared-ClK2vJrs.js";
2
+ import Schema from "@deepseek-ai/schemastery";
3
+ import { defineTool } from "@deepseek-ai/dsh-tools";
4
+ //#region src/index.ts
5
+ /** Host plugin name; must match package.json `name` and the cordis.patch.yml row id. */
6
+ const name = "@quonaro/dsh-plugin-devin";
7
+ /** Services this plugin needs before it loads: the tool registry and a subprocess provider. */
8
+ const inject = ["tools", "subprocess"];
9
+ /** Schemastery schema: defaults live here; cordis.yml and GUI edits are validated against it. */
10
+ const Config = Schema.object({
11
+ devinPath: Schema.string().default("devin").volatile(),
12
+ permissionMode: Schema.union([
13
+ "normal",
14
+ "accept-edits",
15
+ "smart",
16
+ "bypass",
17
+ "autonomous"
18
+ ]).default("accept-edits").volatile(),
19
+ model: Schema.string().default("").volatile(),
20
+ cloud: Schema.boolean().default(false).volatile(),
21
+ timeoutMs: Schema.number().default(6e5).volatile(),
22
+ maxOutputBytes: Schema.number().default(1048576).volatile(),
23
+ respectWorkspaceTrust: Schema.boolean().default(false).volatile(),
24
+ forwardEnv: Schema.array(Schema.string()).default([
25
+ "PATH",
26
+ "HOME",
27
+ "USER",
28
+ "XDG_CONFIG_HOME",
29
+ "XDG_DATA_HOME",
30
+ "WINDSURF_API_KEY"
31
+ ]).volatile(),
32
+ extraArgs: Schema.array(Schema.string()).default([]).volatile()
33
+ });
34
+ /** Spawn `devin -p` once and collect bounded stdout/stderr. Shared by the tool and the command. */
35
+ async function runDevin(ctx, config, options) {
36
+ const env = forwardedEnv(config.forwardEnv.get());
37
+ const executable = await ctx.subprocess.resolveExecutable(config.devinPath.get(), env, options.signal);
38
+ const timeoutMs = options.timeoutMs ?? config.timeoutMs.get();
39
+ const argv = [executable, ...devinPrintArgv({
40
+ model: options.model ?? config.model.get(),
41
+ permissionMode: options.permissionMode ?? config.permissionMode.get(),
42
+ cloud: options.cloud ?? config.cloud.get(),
43
+ resume: options.resume,
44
+ respectWorkspaceTrust: config.respectWorkspaceTrust.get(),
45
+ extraArgs: config.extraArgs.get()
46
+ }, options.prompt)];
47
+ const timeout = AbortSignal.timeout(timeoutMs);
48
+ const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
49
+ const maxBytes = config.maxOutputBytes.get();
50
+ const handle = ctx.subprocess.spawn({
51
+ argv,
52
+ cwd: options.cwd,
53
+ stdio: {
54
+ stdin: "ignore",
55
+ stdout: { maxBytes },
56
+ stderr: { maxBytes }
57
+ },
58
+ graceMs: 1e4,
59
+ signal,
60
+ env
61
+ });
62
+ const outcome = await handle.done;
63
+ const stdout = handle.collected.stdout?.readFrom(0);
64
+ const stderr = handle.collected.stderr?.readFrom(0);
65
+ const timedOut = timeout.aborted;
66
+ return {
67
+ ok: !timedOut && outcome.exitCode === 0,
68
+ exitCode: outcome.exitCode ?? -1,
69
+ signal: outcome.signal ?? "",
70
+ timedOut,
71
+ truncated: Boolean(stdout?.lossy || stderr?.lossy),
72
+ stdout: stdout?.text ?? "",
73
+ stderr: stderr?.text ?? ""
74
+ };
75
+ }
76
+ function renderResult(result) {
77
+ const head = result.timedOut ? "devin timed out and was terminated." : result.ok ? "devin finished successfully." : `devin exited with code ${result.exitCode}${result.signal ? ` (signal ${result.signal})` : ""}.`;
78
+ const tail = result.truncated ? "\n[output truncated — tail shown]" : "";
79
+ return `${head}\n\n${result.stdout || "(no stdout)"}${result.stderr ? `\n\n--- stderr ---\n${result.stderr}` : ""}${tail}`;
80
+ }
81
+ /**
82
+ * Plugin body: register the `devin` tool, and `/devin` when a commands
83
+ * service is composed. All registrations are effects — unloading the plugin
84
+ * reverts them automatically.
85
+ */
86
+ function apply(ctx, config) {
87
+ ctx.tools.register(defineTool({
88
+ name: "devin",
89
+ description: "Delegate a task to the Devin CLI agent (`devin -p`, headless). Devin works autonomously in the given directory and its final answer is returned as text. Prefer this for self-contained chunks of work you want a second agent to own end-to-end. The call blocks until Devin finishes or the configured timeout fires.",
90
+ parameters: {
91
+ prompt: {
92
+ type: "string",
93
+ required: true,
94
+ description: "The task for Devin, written self-contained — it does not see this conversation."
95
+ },
96
+ cwd: {
97
+ type: "string",
98
+ description: "Working directory for the Devin session. Defaults to the current directory."
99
+ },
100
+ model: {
101
+ type: "string",
102
+ description: "Devin model override for this call (e.g. \"opus\"). Defaults to the plugin config value."
103
+ },
104
+ permissionMode: {
105
+ type: "string",
106
+ enum: [
107
+ "normal",
108
+ "accept-edits",
109
+ "smart",
110
+ "bypass",
111
+ "autonomous"
112
+ ],
113
+ description: "Permission mode for the spawned session. Headless runs cannot answer prompts; the default comes from plugin config."
114
+ },
115
+ resume: {
116
+ type: "string",
117
+ description: "Resume an existing Devin session by ID (or cloud session URL with cloud=true) instead of starting a new one."
118
+ },
119
+ cloud: {
120
+ type: "boolean",
121
+ description: "Drive a Devin Cloud session instead of the local agent. Defaults to the plugin config value."
122
+ },
123
+ timeoutMs: {
124
+ type: "number",
125
+ description: "Timeout in milliseconds for this call. Defaults to the plugin config value."
126
+ }
127
+ },
128
+ output: {
129
+ schema: {
130
+ type: "object",
131
+ properties: {
132
+ ok: {
133
+ type: "boolean",
134
+ required: true
135
+ },
136
+ exitCode: {
137
+ type: "integer",
138
+ required: true
139
+ },
140
+ signal: {
141
+ type: "string",
142
+ required: true
143
+ },
144
+ timedOut: {
145
+ type: "boolean",
146
+ required: true
147
+ },
148
+ truncated: {
149
+ type: "boolean",
150
+ required: true
151
+ },
152
+ stdout: {
153
+ type: "string",
154
+ required: true
155
+ },
156
+ stderr: {
157
+ type: "string",
158
+ required: true
159
+ }
160
+ },
161
+ additionalProperties: false
162
+ },
163
+ render: (_args, value) => [{
164
+ type: "text",
165
+ text: renderResult(value)
166
+ }]
167
+ },
168
+ async execute(args, exec) {
169
+ return runDevin(ctx, config, {
170
+ prompt: args.prompt,
171
+ cwd: args.cwd ?? process.cwd(),
172
+ model: args.model,
173
+ permissionMode: args.permissionMode,
174
+ resume: args.resume,
175
+ cloud: args.cloud,
176
+ timeoutMs: args.timeoutMs,
177
+ signal: exec.signal
178
+ });
179
+ },
180
+ presentResult: (_args, result) => ({
181
+ card: "generic",
182
+ title: "devin",
183
+ content: result.content
184
+ })
185
+ }));
186
+ ctx.inject(["commands"], (commandCtx) => {
187
+ commandCtx.commands.register({
188
+ name: "devin",
189
+ description: "Delegate a task to the Devin CLI (devin -p). Usage: /devin <task>",
190
+ handler: async ({ rawInput }) => {
191
+ const prompt = rawInput.trim();
192
+ if (!prompt) return {
193
+ kind: "error",
194
+ text: "Usage: /devin <task for Devin>"
195
+ };
196
+ const result = await runDevin(ctx, config, {
197
+ prompt,
198
+ cwd: process.cwd()
199
+ });
200
+ return {
201
+ kind: result.ok ? "success" : "error",
202
+ text: renderResult(result)
203
+ };
204
+ }
205
+ });
206
+ });
207
+ }
208
+ //#endregion
209
+ export { Config, apply, inject, name };
@@ -0,0 +1,112 @@
1
+ import Schema from "@deepseek-ai/schemastery";
2
+ import { Context, Volatile } from "@deepseek-ai/cordis";
3
+ //#region node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.5/node_modules/@deepseek-ai/cosmokit/lib/types/misc.d.ts
4
+ /** String/symbol keyed dictionary type. */
5
+ type Dict<T = any, K extends string | symbol = string> = { [key in K]: T; };
6
+ //#endregion
7
+ //#region src/provider.d.ts
8
+ /** Plugin name; the cordis.patch.yml row uses the `@quonaro/dsh-plugin-devin/provider` specifier. */
9
+ declare const name = "@quonaro/dsh-plugin-devin/provider";
10
+ /** Services this plugin needs: the llm adapter registry and a subprocess provider. */
11
+ declare const inject: string[];
12
+ /** Plugin configuration; every field is a Volatile re-read at each call. */
13
+ interface Config {
14
+ /** Path or PATH-resolved name of the Devin CLI executable. */
15
+ devinPath: Volatile<string>;
16
+ /** `--permission-mode` for spawned sessions; headless runs cannot answer prompts. */
17
+ permissionMode: Volatile<string>;
18
+ /** Spawn with `--cloud` to drive Devin Cloud sessions instead of the local agent. */
19
+ cloud: Volatile<boolean>;
20
+ /** Working directory for every spawned session; empty = the harness cwd. */
21
+ cwd: Volatile<string>;
22
+ /** Cooperative timeout in milliseconds per generation. Devin runs are slow. */
23
+ timeoutMs: Volatile<number>;
24
+ /** In-memory cap for the captured stderr tail surfaced on failure. */
25
+ stderrMaxBytes: Volatile<number>;
26
+ /** When false, `--respect-workspace-trust false` is passed. */
27
+ respectWorkspaceTrust: Volatile<boolean>;
28
+ /** Environment variable names forwarded to the devin child process. */
29
+ forwardEnv: Volatile<string[]>;
30
+ /** Extra CLI flags appended verbatim before the prompt. */
31
+ extraArgs: Volatile<string[]>;
32
+ /**
33
+ * Advertised models, in selector order. `id` is the route model id passed via
34
+ * GenerateOptions.model; `devinModel` is the `--model` value ('' = the
35
+ * CLI/account default); `name` is the display name in the model selector.
36
+ */
37
+ models: Volatile<{
38
+ id: string;
39
+ devinModel: string;
40
+ name: string;
41
+ }[]>;
42
+ /**
43
+ * Instruction prepended to every flattened transcript, telling Devin it runs
44
+ * as a model backend and must finish autonomously.
45
+ */
46
+ brief: Volatile<string>;
47
+ /** Answer session-title requests locally instead of spending a Devin run on them. */
48
+ localSessionTitles: Volatile<boolean>;
49
+ /** Probe `devin acp` for the account's real model catalog instead of using the static `models` table. */
50
+ autoDiscoverModels: Volatile<boolean>;
51
+ /** Timeout for one ACP discovery probe (initialize + session/new). */
52
+ discoveryTimeoutMs: Volatile<number>;
53
+ /** How long a successful discovery result is cached before re-probing. */
54
+ discoveryCacheMs: Volatile<number>;
55
+ }
56
+ /** Schemastery schema: defaults live here; cordis.yml and GUI edits are validated against it. */
57
+ declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
58
+ devinPath: Schema<string, string, "volatile-defined">;
59
+ permissionMode: Schema<"normal" | "accept-edits" | "smart" | "bypass" | "autonomous", "normal" | "accept-edits" | "smart" | "bypass" | "autonomous", "volatile-defined">;
60
+ cloud: Schema<boolean, boolean, "volatile-defined">;
61
+ cwd: Schema<string, string, "volatile-defined">;
62
+ timeoutMs: Schema<number, number, "volatile-defined">;
63
+ stderrMaxBytes: Schema<number, number, "volatile-defined">;
64
+ respectWorkspaceTrust: Schema<boolean, boolean, "volatile-defined">;
65
+ forwardEnv: Schema<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
66
+ extraArgs: Schema<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
67
+ models: Schema<NoInfer<({
68
+ id?: string | null;
69
+ devinModel?: string | null;
70
+ name?: string | null;
71
+ } & Dict)[]>, NoInfer<Schemastery.ObjectT<NoInfer<{
72
+ id: Schema<string, string, "plain">;
73
+ devinModel: Schema<string, string, "defined">;
74
+ name: Schema<string, string, "defined">;
75
+ }>>[]>, "volatile-defined">;
76
+ brief: Schema<string, string, "volatile-defined">;
77
+ localSessionTitles: Schema<boolean, boolean, "volatile-defined">;
78
+ autoDiscoverModels: Schema<boolean, boolean, "volatile-defined">;
79
+ discoveryTimeoutMs: Schema<number, number, "volatile-defined">;
80
+ discoveryCacheMs: Schema<number, number, "volatile-defined">;
81
+ }>>, Schemastery.ObjectT<NoInfer<{
82
+ devinPath: Schema<string, string, "volatile-defined">;
83
+ permissionMode: Schema<"normal" | "accept-edits" | "smart" | "bypass" | "autonomous", "normal" | "accept-edits" | "smart" | "bypass" | "autonomous", "volatile-defined">;
84
+ cloud: Schema<boolean, boolean, "volatile-defined">;
85
+ cwd: Schema<string, string, "volatile-defined">;
86
+ timeoutMs: Schema<number, number, "volatile-defined">;
87
+ stderrMaxBytes: Schema<number, number, "volatile-defined">;
88
+ respectWorkspaceTrust: Schema<boolean, boolean, "volatile-defined">;
89
+ forwardEnv: Schema<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
90
+ extraArgs: Schema<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
91
+ models: Schema<NoInfer<({
92
+ id?: string | null;
93
+ devinModel?: string | null;
94
+ name?: string | null;
95
+ } & Dict)[]>, NoInfer<Schemastery.ObjectT<NoInfer<{
96
+ id: Schema<string, string, "plain">;
97
+ devinModel: Schema<string, string, "defined">;
98
+ name: Schema<string, string, "defined">;
99
+ }>>[]>, "volatile-defined">;
100
+ brief: Schema<string, string, "volatile-defined">;
101
+ localSessionTitles: Schema<boolean, boolean, "volatile-defined">;
102
+ autoDiscoverModels: Schema<boolean, boolean, "volatile-defined">;
103
+ discoveryTimeoutMs: Schema<number, number, "volatile-defined">;
104
+ discoveryCacheMs: Schema<number, number, "volatile-defined">;
105
+ }>>, "plain">;
106
+ /**
107
+ * Plugin body: register the `devin` provider route and declare it in the
108
+ * configurable-provider directory so the web Models page can see it.
109
+ */
110
+ declare function apply(ctx: Context, config: Config): void;
111
+ //#endregion
112
+ export { Config, type Dict, apply, inject, name };
@@ -0,0 +1,447 @@
1
+ import { n as forwardedEnv, t as devinPrintArgv } from "./shared-ClK2vJrs.js";
2
+ import Schema from "@deepseek-ai/schemastery";
3
+ //#region src/discover.ts
4
+ function isModeOption(c) {
5
+ return c.category === "mode" || c.id === "mode" || c.id.includes("permission_mode") || c.id.includes("cognition/permission");
6
+ }
7
+ function isModelOption(c) {
8
+ return c.category === "model" || !isModeOption(c) && c.id.includes("model");
9
+ }
10
+ /** Flatten a select option's `options` (flat list or grouped sub-lists). */
11
+ function flattenOptions(opt) {
12
+ const out = [];
13
+ for (const entry of opt?.options ?? []) {
14
+ const items = Array.isArray(entry.options) ? entry.options : [entry];
15
+ for (const item of items) {
16
+ if (item.value === void 0 || item.value === null) continue;
17
+ const id = String(item.value);
18
+ out.push({
19
+ id,
20
+ name: item.name ?? item.label ?? id,
21
+ description: item.description
22
+ });
23
+ }
24
+ }
25
+ return out;
26
+ }
27
+ /** Extract the model catalog from a session/new result. */
28
+ function modelsFromSessionNew(result) {
29
+ const configOptions = result?.configOptions;
30
+ if (!Array.isArray(configOptions)) return null;
31
+ const modelOpt = configOptions.find(isModelOption);
32
+ const models = flattenOptions(modelOpt);
33
+ if (models.length === 0) return null;
34
+ return {
35
+ models,
36
+ currentId: modelOpt?.currentValue === void 0 ? void 0 : String(modelOpt.currentValue)
37
+ };
38
+ }
39
+ /**
40
+ * Discover the model catalog by probing `devin acp`.
41
+ * The child is terminated once session/new answers (or the timeout aborts it).
42
+ */
43
+ async function discoverModelsViaAcp(ctx, opts) {
44
+ const env = forwardedEnv(opts.env);
45
+ const executable = await ctx.subprocess.resolveExecutable(opts.devinPath, env, opts.signal);
46
+ const timeout = AbortSignal.timeout(opts.timeoutMs);
47
+ const signal = opts.signal ? AbortSignal.any([opts.signal, timeout]) : timeout;
48
+ const handle = ctx.subprocess.spawn({
49
+ argv: [executable, "acp"],
50
+ cwd: opts.cwd,
51
+ stdio: {
52
+ stdin: "pipe",
53
+ stdout: "pipe",
54
+ stderr: { maxBytes: opts.stderrMaxBytes ?? 65536 }
55
+ },
56
+ graceMs: 5e3,
57
+ signal,
58
+ env
59
+ });
60
+ try {
61
+ const stdin = handle.stdin;
62
+ const stdout = handle.stdout;
63
+ if (!stdin || !stdout) return null;
64
+ const pending = /* @__PURE__ */ new Map();
65
+ let buffer = "";
66
+ let nextId = 1;
67
+ handle.done.then((outcome) => {
68
+ const err = /* @__PURE__ */ new Error(`devin acp exited early (code ${outcome.exitCode ?? `signal ${outcome.signal}`})`);
69
+ for (const p of pending.values()) p.reject(err);
70
+ pending.clear();
71
+ });
72
+ stdout.on("data", (chunk) => {
73
+ buffer += chunk.toString();
74
+ let idx;
75
+ while ((idx = buffer.indexOf("\n")) >= 0) {
76
+ const line = buffer.slice(0, idx);
77
+ buffer = buffer.slice(idx + 1);
78
+ if (!line.trim()) continue;
79
+ let msg;
80
+ try {
81
+ msg = JSON.parse(line);
82
+ } catch {
83
+ continue;
84
+ }
85
+ if (msg.id !== void 0 && msg.method === void 0) {
86
+ const p = pending.get(msg.id);
87
+ if (p) {
88
+ pending.delete(msg.id);
89
+ if (msg.error) p.reject(/* @__PURE__ */ new Error(`[${msg.error.code}] ${msg.error.message}`));
90
+ else p.resolve(msg.result);
91
+ }
92
+ } else if (msg.method !== void 0 && msg.id !== void 0) stdin.write(JSON.stringify({
93
+ jsonrpc: "2.0",
94
+ id: msg.id,
95
+ error: {
96
+ code: -32601,
97
+ message: "not supported by dsh-plugin-devin discovery"
98
+ }
99
+ }) + "\n");
100
+ }
101
+ });
102
+ const request = (method, params) => new Promise((resolve, reject) => {
103
+ const id = nextId++;
104
+ pending.set(id, {
105
+ resolve,
106
+ reject
107
+ });
108
+ stdin.write(JSON.stringify({
109
+ jsonrpc: "2.0",
110
+ id,
111
+ method,
112
+ params
113
+ }) + "\n");
114
+ });
115
+ await request("initialize", {
116
+ protocolVersion: 1,
117
+ clientInfo: {
118
+ name: "@quonaro/dsh-plugin-devin",
119
+ version: "0.1.0"
120
+ },
121
+ clientCapabilities: { fs: {
122
+ readTextFile: true,
123
+ writeTextFile: true
124
+ } }
125
+ });
126
+ return modelsFromSessionNew(await request("session/new", {
127
+ cwd: opts.cwd,
128
+ mcpServers: []
129
+ }));
130
+ } finally {
131
+ handle.terminate();
132
+ await handle.waitForExit(AbortSignal.timeout(6e3)).catch(() => false);
133
+ }
134
+ }
135
+ //#endregion
136
+ //#region src/provider.ts
137
+ /** Plugin name; the cordis.patch.yml row uses the `@quonaro/dsh-plugin-devin/provider` specifier. */
138
+ const name = "@quonaro/dsh-plugin-devin/provider";
139
+ /** Services this plugin needs: the llm adapter registry and a subprocess provider. */
140
+ const inject = ["llm", "subprocess"];
141
+ /** Schemastery schema: defaults live here; cordis.yml and GUI edits are validated against it. */
142
+ const Config = Schema.object({
143
+ devinPath: Schema.string().default("devin").volatile(),
144
+ permissionMode: Schema.union([
145
+ "normal",
146
+ "accept-edits",
147
+ "smart",
148
+ "bypass",
149
+ "autonomous"
150
+ ]).default("accept-edits").volatile(),
151
+ cloud: Schema.boolean().default(false).volatile(),
152
+ cwd: Schema.string().default("").volatile(),
153
+ timeoutMs: Schema.number().default(18e5).volatile(),
154
+ stderrMaxBytes: Schema.number().default(65536).volatile(),
155
+ respectWorkspaceTrust: Schema.boolean().default(false).volatile(),
156
+ forwardEnv: Schema.array(Schema.string()).default([
157
+ "PATH",
158
+ "HOME",
159
+ "USER",
160
+ "XDG_CONFIG_HOME",
161
+ "XDG_DATA_HOME",
162
+ "WINDSURF_API_KEY"
163
+ ]).volatile(),
164
+ extraArgs: Schema.array(Schema.string()).default([]).volatile(),
165
+ models: Schema.array(Schema.object({
166
+ id: Schema.string(),
167
+ devinModel: Schema.string().default(""),
168
+ name: Schema.string().default("")
169
+ })).default([{
170
+ id: "default",
171
+ devinModel: "",
172
+ name: "Devin (account default)"
173
+ }, {
174
+ id: "opus",
175
+ devinModel: "opus",
176
+ name: "Devin Opus"
177
+ }]).volatile(),
178
+ brief: Schema.string().default("You are Devin, an autonomous software-engineering agent, invoked as the model backend of another agent harness. The transcript of the calling conversation is below. Carry out the most recent user request end-to-end using your own tools and judgment. Do not ask questions — act autonomously. Your final printed answer is returned verbatim as the model's reply.").volatile(),
179
+ localSessionTitles: Schema.boolean().default(true).volatile(),
180
+ autoDiscoverModels: Schema.boolean().default(true).volatile(),
181
+ discoveryTimeoutMs: Schema.number().default(6e4).volatile(),
182
+ discoveryCacheMs: Schema.number().default(3e5).volatile()
183
+ });
184
+ const ROLE_LABEL = {
185
+ system: "system",
186
+ developer: "developer",
187
+ user: "user",
188
+ assistant: "assistant",
189
+ tool: "tool result"
190
+ };
191
+ /** Project one message's blocks into transcript text. */
192
+ function blocksToText(blocks) {
193
+ return blocks.map((block) => {
194
+ switch (block.type) {
195
+ case "text": return block.text;
196
+ case "reasoning": return `<thinking>\n${block.text}\n</thinking>`;
197
+ case "tool-call": return `[tool call ${block.name} #${block.id}]: ${block.arguments}`;
198
+ case "image": return "[image]";
199
+ case "file": return "[file]";
200
+ case "tool-addition": return `[tool enabled: ${block.toolName}]`;
201
+ case "tool-removal": return `[tool disabled: ${block.toolName}]`;
202
+ default: return `[${block.type}]`;
203
+ }
204
+ }).join("\n");
205
+ }
206
+ /** Flatten one generation request into the prompt handed to `devin -p`. */
207
+ function buildPrompt(options, brief) {
208
+ const parts = [
209
+ brief,
210
+ "",
211
+ "<transcript>"
212
+ ];
213
+ if (options.system) parts.push("## system", options.system, "");
214
+ for (const message of options.messages) parts.push(`## ${ROLE_LABEL[message.role] ?? message.role}`, blocksToText(message.content), "");
215
+ parts.push("</transcript>");
216
+ if (options.tools?.length) {
217
+ const names = options.tools.map((tool) => tool.name).slice(0, 12).join(", ");
218
+ parts.push("", `[note: the caller declared ${options.tools.length} of its own tools (${names}${options.tools.length > 12 ? ", …" : ""}) — you cannot invoke them; complete the work with your own capabilities.]`);
219
+ }
220
+ return parts.join("\n");
221
+ }
222
+ /** Map an advertised route id to its `--model` value; unknown ids pass through verbatim. */
223
+ function resolveDevinModel(routeId, table) {
224
+ const entry = table.find((m) => m.id === routeId);
225
+ if (entry) return entry.devinModel;
226
+ return routeId === "default" ? "" : routeId;
227
+ }
228
+ /** Last user text, for cheap local session titles. */
229
+ function lastUserText(messages) {
230
+ for (let i = messages.length - 1; i >= 0; i--) {
231
+ const message = messages[i];
232
+ if (message.role === "user") {
233
+ const text = blocksToText(message.content).replace(/\s+/g, " ").trim();
234
+ if (text) return text;
235
+ }
236
+ }
237
+ return "Devin session";
238
+ }
239
+ /**
240
+ * The provider-wire adapter. Duck-typed against the documented LlmAdapter
241
+ * surface — the registry validates behavior, not inheritance — so this class
242
+ * carries no runtime import of @deepseek-ai/dsh-llm.
243
+ */
244
+ var DevinLlmAdapter = class {
245
+ ctx;
246
+ config;
247
+ constructor(ctx, config) {
248
+ this.ctx = ctx;
249
+ this.config = config;
250
+ }
251
+ providerInfo(provider) {
252
+ return {
253
+ id: provider,
254
+ name: "Devin"
255
+ };
256
+ }
257
+ providerRetryPolicy(_provider) {}
258
+ imageRequestPricing(_provider, _model) {}
259
+ discovered;
260
+ /** Probe `devin acp` for the real catalog (cached); null on failure/disabled. */
261
+ async discover() {
262
+ if (!this.config.autoDiscoverModels.get()) return null;
263
+ const cacheMs = this.config.discoveryCacheMs.get();
264
+ if (this.discovered && Date.now() - this.discovered.at < cacheMs) return this.discovered.result;
265
+ try {
266
+ const result = await discoverModelsViaAcp(this.ctx, {
267
+ devinPath: this.config.devinPath.get(),
268
+ env: this.config.forwardEnv.get(),
269
+ cwd: this.config.cwd.get() || process.cwd(),
270
+ timeoutMs: this.config.discoveryTimeoutMs.get(),
271
+ stderrMaxBytes: this.config.stderrMaxBytes.get()
272
+ });
273
+ if (result) this.discovered = {
274
+ at: Date.now(),
275
+ result
276
+ };
277
+ return result;
278
+ } catch {
279
+ return null;
280
+ }
281
+ }
282
+ async listModels(provider) {
283
+ const discovered = await this.discover();
284
+ if (discovered && discovered.models.length > 0) {
285
+ const currentId = discovered.currentId;
286
+ return [{
287
+ provider,
288
+ id: "default",
289
+ name: "Devin (account default)",
290
+ description: currentId ? `Resolves to the session default (${currentId})` : "Devin CLI agent, spawned headless per generation",
291
+ inputModalities: ["text"]
292
+ }, ...discovered.models.map((m) => ({
293
+ provider,
294
+ id: m.id,
295
+ name: m.name,
296
+ description: m.description ?? "Devin CLI agent, spawned headless per generation",
297
+ inputModalities: ["text"]
298
+ }))];
299
+ }
300
+ return this.config.models.get().map((entry) => ({
301
+ provider,
302
+ id: entry.id,
303
+ name: entry.name || `Devin ${entry.id}`,
304
+ description: "Devin CLI agent, spawned headless per generation",
305
+ inputModalities: ["text"]
306
+ }));
307
+ }
308
+ resolveModel(provider, model, _signal) {
309
+ return Promise.resolve({
310
+ provider,
311
+ id: model,
312
+ name: `Devin ${model}`,
313
+ inputModalities: ["text"]
314
+ });
315
+ }
316
+ async prepareCall(provider, model, signal) {
317
+ return {
318
+ model: await this.resolveModel(provider, model, signal),
319
+ stream: (options) => this.stream(options)
320
+ };
321
+ }
322
+ async *stream(options) {
323
+ if (options.purpose === "session-title" && this.config.localSessionTitles.get()) {
324
+ const text = lastUserText(options.messages).slice(0, 80);
325
+ yield {
326
+ type: "block-start",
327
+ index: 0,
328
+ blockType: "text"
329
+ };
330
+ yield {
331
+ type: "text-delta",
332
+ index: 0,
333
+ text
334
+ };
335
+ yield {
336
+ type: "block-end",
337
+ index: 0,
338
+ block: {
339
+ type: "text",
340
+ text
341
+ }
342
+ };
343
+ yield {
344
+ type: "finish",
345
+ reason: { kind: "stop" }
346
+ };
347
+ return;
348
+ }
349
+ const env = forwardedEnv(this.config.forwardEnv.get());
350
+ const executable = await this.ctx.subprocess.resolveExecutable(this.config.devinPath.get(), env, options.signal);
351
+ const timeout = AbortSignal.timeout(this.config.timeoutMs.get());
352
+ const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
353
+ const prompt = buildPrompt(options, this.config.brief.get());
354
+ const argv = [executable, ...devinPrintArgv({
355
+ model: resolveDevinModel(options.model, this.config.models.get()),
356
+ permissionMode: this.config.permissionMode.get(),
357
+ cloud: this.config.cloud.get(),
358
+ respectWorkspaceTrust: this.config.respectWorkspaceTrust.get(),
359
+ extraArgs: this.config.extraArgs.get()
360
+ }, prompt)];
361
+ const handle = this.ctx.subprocess.spawn({
362
+ argv,
363
+ cwd: this.config.cwd.get() || process.cwd(),
364
+ stdio: {
365
+ stdin: "ignore",
366
+ stdout: "pipe",
367
+ stderr: { maxBytes: this.config.stderrMaxBytes.get() }
368
+ },
369
+ graceMs: 1e4,
370
+ signal,
371
+ env
372
+ });
373
+ let text = "";
374
+ yield {
375
+ type: "block-start",
376
+ index: 0,
377
+ blockType: "text"
378
+ };
379
+ const stdout = handle.stdout;
380
+ if (stdout) for await (const chunk of stdout) {
381
+ const piece = new TextDecoder().decode(chunk);
382
+ text += piece;
383
+ yield {
384
+ type: "text-delta",
385
+ index: 0,
386
+ text: piece
387
+ };
388
+ }
389
+ const outcome = await handle.done;
390
+ yield {
391
+ type: "block-end",
392
+ index: 0,
393
+ block: {
394
+ type: "text",
395
+ text
396
+ }
397
+ };
398
+ yield {
399
+ type: "usage",
400
+ usage: {
401
+ inputTokens: Math.ceil(prompt.length / 4),
402
+ outputTokens: Math.ceil(text.length / 4)
403
+ }
404
+ };
405
+ if (timeout.aborted) yield {
406
+ type: "finish",
407
+ reason: {
408
+ kind: "error",
409
+ failure: {
410
+ code: "DEVIN_TIMEOUT",
411
+ message: `devin exceeded timeoutMs=${this.config.timeoutMs.get()} and was terminated`
412
+ }
413
+ }
414
+ };
415
+ else if (outcome.exitCode !== 0) {
416
+ const stderr = handle.collected.stderr?.readFrom(0)?.text ?? "";
417
+ yield {
418
+ type: "finish",
419
+ reason: {
420
+ kind: "error",
421
+ failure: {
422
+ code: "DEVIN_EXIT",
423
+ message: `devin exited ${outcome.exitCode ?? `signal ${outcome.signal}`}${stderr ? ` — ${stderr.slice(-400)}` : ""}`
424
+ }
425
+ }
426
+ };
427
+ } else yield {
428
+ type: "finish",
429
+ reason: { kind: "stop" }
430
+ };
431
+ }
432
+ };
433
+ /**
434
+ * Plugin body: register the `devin` provider route and declare it in the
435
+ * configurable-provider directory so the web Models page can see it.
436
+ */
437
+ function apply(ctx, config) {
438
+ ctx.llm.registerAdapter(["devin"], new DevinLlmAdapter(ctx, config));
439
+ ctx.llm.registerConfigurableProviders([{
440
+ provider: "devin",
441
+ displayName: "Devin",
442
+ settingsNs: name,
443
+ settingsPath: []
444
+ }]);
445
+ }
446
+ //#endregion
447
+ export { Config, apply, inject, name };
@@ -0,0 +1,27 @@
1
+ //#region src/shared.ts
2
+ /** Collect the env entries a forwardEnv list names, skipping unset variables. */
3
+ function forwardedEnv(names) {
4
+ const env = {};
5
+ for (const key of names) {
6
+ const value = process.env[key];
7
+ if (value !== void 0) env[key] = value;
8
+ }
9
+ return env;
10
+ }
11
+ /**
12
+ * Assemble `devin --print` argv (without the executable itself).
13
+ * The prompt is always last, behind `--`, so it can never parse as a flag.
14
+ */
15
+ function devinPrintArgv(flags, prompt) {
16
+ const argv = [];
17
+ if (flags.model) argv.push("--model", flags.model);
18
+ if (flags.permissionMode) argv.push("--permission-mode", flags.permissionMode);
19
+ if (flags.cloud) argv.push("--cloud");
20
+ if (flags.resume) argv.push("--resume", flags.resume);
21
+ if (!flags.respectWorkspaceTrust) argv.push("--respect-workspace-trust", "false");
22
+ if (flags.extraArgs) argv.push(...flags.extraArgs);
23
+ argv.push("-p", "--", prompt);
24
+ return argv;
25
+ }
26
+ //#endregion
27
+ export { forwardedEnv as n, devinPrintArgv as t };
package/package.json CHANGED
@@ -1,6 +1,79 @@
1
1
  {
2
2
  "name": "@quonaro/dsh-plugin-devin",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Devin CLI inside DeepSeek Harness: a `devin` delegation tool, a /devin command, and a `devin` LLM provider route — all over the ctx.subprocess seam.",
5
+ "keywords": [
6
+ "deepseek-harness",
7
+ "dsh",
8
+ "dsh-plugin",
9
+ "cordis",
10
+ "devin",
11
+ "subagent",
12
+ "delegation"
13
+ ],
14
+ "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/quonaro/dsh-plugin-devin.git"
18
+ },
19
+ "bugs": "https://github.com/quonaro/dsh-plugin-devin/issues",
20
+ "homepage": "https://github.com/quonaro/dsh-plugin-devin#readme",
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "type": "module",
25
+ "main": "lib/index.js",
26
+ "types": "lib/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./lib/index.d.ts",
30
+ "default": "./lib/index.js"
31
+ },
32
+ "./cordis.patch.yml": "./cordis.patch.yml",
33
+ "./package.json": "./package.json",
34
+ "./provider": {
35
+ "types": "./lib/provider.d.ts",
36
+ "default": "./lib/provider.js"
37
+ }
38
+ },
39
+ "files": [
40
+ "lib/",
41
+ "cordis.patch.yml"
42
+ ],
43
+ "dsh": {
44
+ "bundle": {
45
+ "patch": "./cordis.patch.yml"
46
+ }
47
+ },
48
+ "engines": {
49
+ "node": "^22.19.0 || >=24.0.0"
50
+ },
51
+ "scripts": {
52
+ "build": "tsdown",
53
+ "typecheck": "tsc --noEmit",
54
+ "clean": "rm -rf lib",
55
+ "smoke": "node test/smoke.mjs && node test/smoke-provider.mjs && node test/smoke-discover.mjs",
56
+ "test": "pnpm typecheck && pnpm build && pnpm smoke",
57
+ "prepare": "tsdown"
58
+ },
59
+ "dependencies": {
60
+ "@deepseek-ai/schemastery": "~3.18.4"
61
+ },
62
+ "peerDependencies": {
63
+ "@deepseek-ai/cordis": "^4.0.4",
64
+ "@deepseek-ai/dsh-llm": "^0.2.0-rc.2",
65
+ "@deepseek-ai/dsh-subprocess": "^0.2.0-rc.2",
66
+ "@deepseek-ai/dsh-tools": "^0.2.0-rc.2"
67
+ },
68
+ "devDependencies": {
69
+ "@deepseek-ai/cordis": "~4.0.4",
70
+ "@deepseek-ai/cosmokit": "^1.8.5",
71
+ "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
72
+ "@deepseek-ai/dsh-subprocess": "0.2.0-rc.2",
73
+ "@deepseek-ai/dsh-tools": "0.2.0-rc.2",
74
+ "@deepseek-ai/schemastery": "~3.18.4",
75
+ "@types/node": "^22.20.0",
76
+ "tsdown": "^0.22.2",
77
+ "typescript": "^6.0.3"
78
+ }
79
+ }