@mrpatronz/nexusflow 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -8
- package/dist/agent/AntigravityCliAdapter.d.ts +7 -0
- package/dist/agent/AntigravityCliAdapter.d.ts.map +1 -0
- package/dist/agent/AntigravityCliAdapter.js +13 -0
- package/dist/agent/AntigravityCliAdapter.js.map +1 -0
- package/dist/agent/ClaudeCliAdapter.d.ts +16 -0
- package/dist/agent/ClaudeCliAdapter.d.ts.map +1 -0
- package/dist/agent/ClaudeCliAdapter.js +34 -0
- package/dist/agent/ClaudeCliAdapter.js.map +1 -0
- package/dist/agent/CliAdapterBase.d.ts +48 -0
- package/dist/agent/CliAdapterBase.d.ts.map +1 -0
- package/dist/agent/CliAdapterBase.js +120 -0
- package/dist/agent/CliAdapterBase.js.map +1 -0
- package/dist/agent/NativeAgent.d.ts +13 -0
- package/dist/agent/NativeAgent.d.ts.map +1 -0
- package/dist/agent/NativeAgent.js +97 -0
- package/dist/agent/NativeAgent.js.map +1 -0
- package/dist/agent/NativeAgentBase.d.ts +27 -0
- package/dist/agent/NativeAgentBase.d.ts.map +1 -0
- package/dist/agent/NativeAgentBase.js +58 -0
- package/dist/agent/NativeAgentBase.js.map +1 -0
- package/dist/agent/NativeClaudeAgent.d.ts +13 -0
- package/dist/agent/NativeClaudeAgent.d.ts.map +1 -0
- package/dist/agent/NativeClaudeAgent.js +115 -0
- package/dist/agent/NativeClaudeAgent.js.map +1 -0
- package/dist/agent/NativeGoogleAgent.d.ts +13 -0
- package/dist/agent/NativeGoogleAgent.d.ts.map +1 -0
- package/dist/agent/NativeGoogleAgent.js +90 -0
- package/dist/agent/NativeGoogleAgent.js.map +1 -0
- package/dist/agent/ProviderRegistry.d.ts +31 -0
- package/dist/agent/ProviderRegistry.d.ts.map +1 -0
- package/dist/agent/ProviderRegistry.js +20 -0
- package/dist/agent/ProviderRegistry.js.map +1 -0
- package/dist/agent/ProviderRegistry.test.d.ts +2 -0
- package/dist/agent/ProviderRegistry.test.d.ts.map +1 -0
- package/dist/agent/ProviderRegistry.test.js +57 -0
- package/dist/agent/ProviderRegistry.test.js.map +1 -0
- package/dist/agent/adapters.d.ts +3 -0
- package/dist/agent/adapters.d.ts.map +1 -0
- package/dist/agent/adapters.js +48 -0
- package/dist/agent/adapters.js.map +1 -0
- package/dist/agent/nativeAgent.test.d.ts +2 -0
- package/dist/agent/nativeAgent.test.d.ts.map +1 -0
- package/dist/agent/nativeAgent.test.js +35 -0
- package/dist/agent/nativeAgent.test.js.map +1 -0
- package/dist/agent/nativeTools.d.ts +26 -0
- package/dist/agent/nativeTools.d.ts.map +1 -0
- package/dist/agent/nativeTools.js +62 -0
- package/dist/agent/nativeTools.js.map +1 -0
- package/dist/agent/nativeTools.test.d.ts +2 -0
- package/dist/agent/nativeTools.test.d.ts.map +1 -0
- package/dist/agent/nativeTools.test.js +67 -0
- package/dist/agent/nativeTools.test.js.map +1 -0
- package/dist/agent/session.d.ts +25 -0
- package/dist/agent/session.d.ts.map +1 -0
- package/dist/agent/session.js +31 -0
- package/dist/agent/session.js.map +1 -0
- package/dist/agent/session.test.d.ts +2 -0
- package/dist/agent/session.test.d.ts.map +1 -0
- package/dist/agent/session.test.js +44 -0
- package/dist/agent/session.test.js.map +1 -0
- package/dist/commands/desktop.d.ts +4 -5
- package/dist/commands/desktop.d.ts.map +1 -1
- package/dist/commands/desktop.js +35 -89
- package/dist/commands/desktop.js.map +1 -1
- package/dist/commands/ui.d.ts +7 -3
- package/dist/commands/ui.d.ts.map +1 -1
- package/dist/commands/ui.js +12 -6
- package/dist/commands/ui.js.map +1 -1
- package/dist/desktop-server.d.ts +13 -0
- package/dist/desktop-server.d.ts.map +1 -0
- package/dist/desktop-server.js +23 -0
- package/dist/desktop-server.js.map +1 -0
- package/dist/gui/assets/index-CQTEzHj2.css +1 -0
- package/dist/gui/assets/index-DLmLdpqC.js +58 -0
- package/dist/gui/index.html +2 -2
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +109 -2
- package/dist/server.js.map +1 -1
- package/dist/utils/session-finder.d.ts +21 -1
- package/dist/utils/session-finder.d.ts.map +1 -1
- package/dist/utils/session-finder.js +220 -143
- package/dist/utils/session-finder.js.map +1 -1
- package/dist/utils/session-finder.test.d.ts +2 -0
- package/dist/utils/session-finder.test.d.ts.map +1 -0
- package/dist/utils/session-finder.test.js +65 -0
- package/dist/utils/session-finder.test.js.map +1 -0
- package/package.json +10 -1
- package/dist/gui/assets/index-CB-Ekvrs.css +0 -1
- package/dist/gui/assets/index-CixLsYSa.js +0 -26
package/README.md
CHANGED
|
@@ -76,8 +76,8 @@ nexusflow init
|
|
|
76
76
|
# 2 — Create a feature workspace
|
|
77
77
|
nexusflow create
|
|
78
78
|
|
|
79
|
-
# 3 — Or
|
|
80
|
-
nexusflow
|
|
79
|
+
# 3 — Or use the GUI: desktop app (see desktop/) or browser dashboard
|
|
80
|
+
nexusflow dashboard
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
The `create` wizard walks you through:
|
|
@@ -120,13 +120,14 @@ Open this folder in your editor → your AI assistant picks up the context → i
|
|
|
120
120
|
| `nexusflow stop` | Stop all running services |
|
|
121
121
|
| `nexusflow status` | Show running/stopped status and PIDs |
|
|
122
122
|
| `nexusflow logs` | Tail aggregated logs from all services |
|
|
123
|
-
| `nexusflow ui` |
|
|
124
|
-
| `nexusflow dashboard` |
|
|
123
|
+
| `nexusflow ui` | Start the dashboard server — the backend the desktop app embeds (`--port`, `--open`, `--strict-port`) |
|
|
124
|
+
| `nexusflow dashboard` | Open the dashboard in your browser (alias: `dash`) |
|
|
125
125
|
| `nexusflow tui` | Open the interactive terminal (TUI) dashboard |
|
|
126
126
|
| `nexusflow diff` | View changes across all sub-repositories, including unpushed commits (`--repo` to filter) |
|
|
127
127
|
| `nexusflow commit` | Commit and push changes across all modified repositories (`--repo`, `--no-push`, `--dry-run`) |
|
|
128
128
|
| `nexusflow sync` | Rebase all repositories in the workspace with default base branches |
|
|
129
129
|
| `nexusflow finish` | Close out a feature: commit & push all repos, open PRs / print compare links, promote learnings, optionally remove the workspace (`-m`, `--no-pr`, `--no-knowledge`, `--cleanup`, `--dry-run`) |
|
|
130
|
+
| `nexusflow review` | Start an iterative reviewer-implementer agent loop with automated verification harnesses |
|
|
130
131
|
| `nexusflow knowledge` | Capture & manage workspace learnings: `add` (decision/gotcha/progress/…), `show`, `promote` into per-repo base knowledge |
|
|
131
132
|
| `nexusflow refresh`| Regenerate maps, plan, and AI context files — only re-analyzes changed repos (`--force` for a full pass) |
|
|
132
133
|
| `nexusflow handoff` | Generate a compact handoff bundle (`nexusflow-handoff.md`) for session resumption |
|
|
@@ -135,7 +136,7 @@ Open this folder in your editor → your AI assistant picks up the context → i
|
|
|
135
136
|
| `nexusflow config` | View and update configuration: `show`, `get <key>`, `set <key> <value>` |
|
|
136
137
|
| `nexusflow adapter` | Manage storage adapters: `list`, `use`, `info`, `init` |
|
|
137
138
|
| `nexusflow mcp` | Manage the MCP server for AI assistants: `run`, `setup` |
|
|
138
|
-
| `nexusflow desktop` | Launch the
|
|
139
|
+
| `nexusflow desktop` | Launch the Electron desktop app from the workspace `desktop/` project (requires a built CLI and `desktop/` deps installed) |
|
|
139
140
|
|
|
140
141
|
## 🤖 Supported AI Assistants
|
|
141
142
|
|
|
@@ -260,7 +261,7 @@ nexusflow schedule disable <id>
|
|
|
260
261
|
nexusflow schedule run <id>
|
|
261
262
|
```
|
|
262
263
|
|
|
263
|
-
Jobs are stored in `~/.nexusflow/schedules.json` and executed while a NexusFlow server is running — start one with `nexusflow ui` (use `--daemon
|
|
264
|
+
Jobs are stored in `~/.nexusflow/schedules.json` and executed while a NexusFlow server is running — start one with `nexusflow ui` (use `--daemon` for a background host). A job whose interval elapsed while no server was running simply runs on the next scheduler tick.
|
|
264
265
|
|
|
265
266
|
Scheduled runs are **token-efficient by design**: they use the same analysis cache as `nexusflow refresh`, so only repos whose content changed are re-analyzed, and unchanged context files are left byte-identical (no git churn, no invalidated AI prompt caches). The dashboard API exposes the same functionality under `/api/schedules`.
|
|
266
267
|
|
|
@@ -283,9 +284,9 @@ The dashboard integrates an **AI Strategy Analysis** inspector:
|
|
|
283
284
|
2. (Optional) Provide a comment or specific evaluation focus (e.g. *"Ensure subagent roles are distinct"*).
|
|
284
285
|
3. Click **Inspect Strategy** to have the AI analyze the strategy rules, identify ambiguities/contradictions, rate its orchestration effectiveness, and generate an optimized rewritten guideline.
|
|
285
286
|
|
|
286
|
-
##
|
|
287
|
+
## 🖥️ Dashboard (Desktop App & Browser)
|
|
287
288
|
|
|
288
|
-
|
|
289
|
+
The primary GUI is the Electron desktop app in `desktop/` (`npm install && npm start` there). It embeds the same dashboard the browser sees. For browser access, run `nexusflow dashboard`; `nexusflow ui` starts the underlying server without opening anything (add `--open` to launch a browser). For the platform rationale (Electron vs Tauri and friends), see [docs/desktop-platform.md](docs/desktop-platform.md). The dashboard is a full-featured dark-themed GUI:
|
|
289
290
|
|
|
290
291
|
- **Workspaces tab** — create, browse, and manage feature workspaces
|
|
291
292
|
- **Sessions tab** — view past AI conversation transcripts and resume sessions
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CliAdapterBase } from './CliAdapterBase.js';
|
|
2
|
+
export declare class AntigravityCliAdapter extends CliAdapterBase {
|
|
3
|
+
protected readonly binary = "agy";
|
|
4
|
+
protected readonly label = "agy CLI";
|
|
5
|
+
protected buildArgs(isFirstTurn: boolean, prompt: string): string[];
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=AntigravityCliAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AntigravityCliAdapter.d.ts","sourceRoot":"","sources":["../../src/agent/AntigravityCliAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,qBAAa,qBAAsB,SAAQ,cAAc;IACvD,SAAS,CAAC,QAAQ,CAAC,MAAM,SAAS;IAClC,SAAS,CAAC,QAAQ,CAAC,KAAK,aAAa;IAMrC,SAAS,CAAC,SAAS,CAAC,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE;CAGpE"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CliAdapterBase } from './CliAdapterBase.js';
|
|
2
|
+
export class AntigravityCliAdapter extends CliAdapterBase {
|
|
3
|
+
binary = 'agy';
|
|
4
|
+
label = 'agy CLI';
|
|
5
|
+
// agy is a native binary: reads the prompt from argv and needs no shell
|
|
6
|
+
// (argv is passed literally, so multi-word prompts are safe without quoting).
|
|
7
|
+
// -p runs a single prompt; -c continues the conversation for later turns.
|
|
8
|
+
// agy has no resume-by-id, so no fallback.
|
|
9
|
+
buildArgs(isFirstTurn, prompt) {
|
|
10
|
+
return isFirstTurn ? ['-p', prompt] : ['-c', '-p', prompt];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=AntigravityCliAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AntigravityCliAdapter.js","sourceRoot":"","sources":["../../src/agent/AntigravityCliAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,MAAM,OAAO,qBAAsB,SAAQ,cAAc;IACpC,MAAM,GAAG,KAAK,CAAC;IACf,KAAK,GAAG,SAAS,CAAC;IACrC,wEAAwE;IACxE,8EAA8E;IAE9E,0EAA0E;IAC1E,2CAA2C;IACjC,SAAS,CAAC,WAAoB,EAAE,MAAc;QACtD,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAC7D,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CliAdapterBase } from './CliAdapterBase.js';
|
|
2
|
+
import { type AgentSession } from './session.js';
|
|
3
|
+
export declare class ClaudeCliAdapter extends CliAdapterBase {
|
|
4
|
+
protected readonly binary = "claude";
|
|
5
|
+
protected readonly label = "claude CLI";
|
|
6
|
+
protected readonly useShell = true;
|
|
7
|
+
protected readonly promptViaStdin = true;
|
|
8
|
+
protected fallbackMessage: string;
|
|
9
|
+
private session;
|
|
10
|
+
private sessionEstablished;
|
|
11
|
+
start(cwd: string, session?: AgentSession): Promise<void>;
|
|
12
|
+
protected buildArgs(isFirstTurn: boolean): string[];
|
|
13
|
+
protected fallbackArgs(): string[] | null;
|
|
14
|
+
protected onTurnComplete(succeeded: boolean): void;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=ClaudeCliAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClaudeCliAdapter.d.ts","sourceRoot":"","sources":["../../src/agent/ClaudeCliAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAEtE,qBAAa,gBAAiB,SAAQ,cAAc;IAClD,SAAS,CAAC,QAAQ,CAAC,MAAM,YAAY;IACrC,SAAS,CAAC,QAAQ,CAAC,KAAK,gBAAgB;IAGxC,SAAS,CAAC,QAAQ,CAAC,QAAQ,QAAQ;IACnC,SAAS,CAAC,QAAQ,CAAC,cAAc,QAAQ;IACzC,SAAS,CAAC,eAAe,SAAiE;IAE1F,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,kBAAkB,CAAkB;IAE/B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;IAMtD,SAAS,CAAC,SAAS,CAAC,WAAW,EAAE,OAAO,GAAG,MAAM,EAAE;IAMnD,SAAS,CAAC,YAAY,IAAI,MAAM,EAAE,GAAG,IAAI;IAOzC,SAAS,CAAC,cAAc,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI;CAGnD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { CliAdapterBase } from './CliAdapterBase.js';
|
|
2
|
+
import { buildClaudeTurnArgs } from './session.js';
|
|
3
|
+
export class ClaudeCliAdapter extends CliAdapterBase {
|
|
4
|
+
binary = 'claude';
|
|
5
|
+
label = 'claude CLI';
|
|
6
|
+
// claude is an npm .cmd shim: needs a shell on Windows, and under a shell an
|
|
7
|
+
// argv prompt would split on spaces, so the prompt goes over stdin.
|
|
8
|
+
useShell = true;
|
|
9
|
+
promptViaStdin = true;
|
|
10
|
+
fallbackMessage = 'Could not resume the previous session — starting a new one.';
|
|
11
|
+
session;
|
|
12
|
+
sessionEstablished = false;
|
|
13
|
+
async start(cwd, session) {
|
|
14
|
+
await super.start(cwd);
|
|
15
|
+
this.session = session;
|
|
16
|
+
this.sessionEstablished = false;
|
|
17
|
+
}
|
|
18
|
+
buildArgs(isFirstTurn) {
|
|
19
|
+
return buildClaudeTurnArgs(isFirstTurn, this.session);
|
|
20
|
+
}
|
|
21
|
+
// A --resume of a purged session fails on the very first turn; the id is
|
|
22
|
+
// then free, so one retry with --session-id recreates it under the same id.
|
|
23
|
+
fallbackArgs() {
|
|
24
|
+
if (this.session?.resume === true && !this.sessionEstablished) {
|
|
25
|
+
return ['-p', '--session-id', this.session.id];
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
onTurnComplete(succeeded) {
|
|
30
|
+
if (succeeded)
|
|
31
|
+
this.sessionEstablished = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=ClaudeCliAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClaudeCliAdapter.js","sourceRoot":"","sources":["../../src/agent/ClaudeCliAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAqB,MAAM,cAAc,CAAC;AAEtE,MAAM,OAAO,gBAAiB,SAAQ,cAAc;IAC/B,MAAM,GAAG,QAAQ,CAAC;IAClB,KAAK,GAAG,YAAY,CAAC;IACxC,6EAA6E;IAC7E,oEAAoE;IACjD,QAAQ,GAAG,IAAI,CAAC;IAChB,cAAc,GAAG,IAAI,CAAC;IAC/B,eAAe,GAAG,6DAA6D,CAAC;IAElF,OAAO,CAA2B;IAClC,kBAAkB,GAAY,KAAK,CAAC;IAErC,KAAK,CAAC,KAAK,CAAC,GAAW,EAAE,OAAsB;QACpD,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;IAClC,CAAC;IAES,SAAS,CAAC,WAAoB;QACtC,OAAO,mBAAmB,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACxD,CAAC;IAED,yEAAyE;IACzE,4EAA4E;IAClE,YAAY;QACpB,IAAI,IAAI,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9D,OAAO,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAES,cAAc,CAAC,SAAkB;QACzC,IAAI,SAAS;YAAE,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAChD,CAAC;CACF"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { type ChildProcess } from 'node:child_process';
|
|
3
|
+
/**
|
|
4
|
+
* Shared lifecycle for CLI-backed agents (claude, agy). Each turn spawns the
|
|
5
|
+
* binary once in print mode, streams stdout as `data`, and emits `idle` when
|
|
6
|
+
* the turn ends. Subclasses supply the binary, the per-turn args, how the
|
|
7
|
+
* prompt is delivered, and (optionally) a one-shot fallback used when the
|
|
8
|
+
* first attempt of a turn fails with no output.
|
|
9
|
+
*
|
|
10
|
+
* Prompt delivery differs per CLI: `claude` is an npm `.cmd` shim that needs
|
|
11
|
+
* `shell: true` on Windows, and under a shell an argv prompt would be split on
|
|
12
|
+
* spaces, so its prompt goes over stdin. `agy` is a native binary that reads
|
|
13
|
+
* the prompt from argv and needs no shell (argv is passed literally, so no
|
|
14
|
+
* quoting hazard).
|
|
15
|
+
*/
|
|
16
|
+
export declare abstract class CliAdapterBase extends EventEmitter {
|
|
17
|
+
protected cwd: string;
|
|
18
|
+
protected isFirstTurn: boolean;
|
|
19
|
+
private isProcessing;
|
|
20
|
+
private stopped;
|
|
21
|
+
private child;
|
|
22
|
+
/** Executable name, e.g. 'claude' or 'agy'. */
|
|
23
|
+
protected abstract readonly binary: string;
|
|
24
|
+
/** Short label for stderr/error diagnostics. */
|
|
25
|
+
protected abstract readonly label: string;
|
|
26
|
+
/** Spawn through a shell (needed to resolve a `.cmd` shim on Windows). */
|
|
27
|
+
protected readonly useShell: boolean;
|
|
28
|
+
/** Pipe the prompt to stdin (true) vs. include it in argv via buildArgs (false). */
|
|
29
|
+
protected readonly promptViaStdin: boolean;
|
|
30
|
+
/** Args for one print-mode turn. Receives the prompt so argv-mode CLIs can
|
|
31
|
+
* include it; stdin-mode CLIs ignore it. */
|
|
32
|
+
protected abstract buildArgs(isFirstTurn: boolean, prompt: string): string[];
|
|
33
|
+
/**
|
|
34
|
+
* Args to retry with when the first attempt of a turn exits non-zero with no
|
|
35
|
+
* output (e.g. resuming a purged session). Return null for no fallback.
|
|
36
|
+
*/
|
|
37
|
+
protected fallbackArgs(): string[] | null;
|
|
38
|
+
/** Message emitted as a `system` event when the fallback kicks in. */
|
|
39
|
+
protected fallbackMessage: string;
|
|
40
|
+
/** Called once per turn after it settles; `succeeded` if it produced output or exited 0. */
|
|
41
|
+
protected onTurnComplete(_succeeded: boolean): void;
|
|
42
|
+
start(cwd: string): Promise<void>;
|
|
43
|
+
send(data: string): Promise<void>;
|
|
44
|
+
private runTurn;
|
|
45
|
+
stop(): void;
|
|
46
|
+
}
|
|
47
|
+
export declare function killTree(child: ChildProcess | null): void;
|
|
48
|
+
//# sourceMappingURL=CliAdapterBase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CliAdapterBase.d.ts","sourceRoot":"","sources":["../../src/agent/CliAdapterBase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE9D;;;;;;;;;;;;GAYG;AACH,8BAAsB,cAAe,SAAQ,YAAY;IACvD,SAAS,CAAC,GAAG,EAAE,MAAM,CAAM;IAC3B,SAAS,CAAC,WAAW,EAAE,OAAO,CAAQ;IACtC,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,KAAK,CAA6B;IAE1C,+CAA+C;IAC/C,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAC3C,gDAAgD;IAChD,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAC1C,0EAA0E;IAC1E,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAS;IAC7C,oFAAoF;IACpF,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAS;IAEnD;iDAC6C;IAC7C,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE;IAE5E;;;OAGG;IACH,SAAS,CAAC,YAAY,IAAI,MAAM,EAAE,GAAG,IAAI;IAIzC,sEAAsE;IACtE,SAAS,CAAC,eAAe,EAAE,MAAM,CAAe;IAEhD,4FAA4F;IAC5F,SAAS,CAAC,cAAc,CAAC,UAAU,EAAE,OAAO,GAAG,IAAI;IAEtC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU9C,OAAO,CAAC,OAAO;IA0DR,IAAI,IAAI,IAAI;CAOpB;AAID,wBAAgB,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,QAOlD"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
/**
|
|
4
|
+
* Shared lifecycle for CLI-backed agents (claude, agy). Each turn spawns the
|
|
5
|
+
* binary once in print mode, streams stdout as `data`, and emits `idle` when
|
|
6
|
+
* the turn ends. Subclasses supply the binary, the per-turn args, how the
|
|
7
|
+
* prompt is delivered, and (optionally) a one-shot fallback used when the
|
|
8
|
+
* first attempt of a turn fails with no output.
|
|
9
|
+
*
|
|
10
|
+
* Prompt delivery differs per CLI: `claude` is an npm `.cmd` shim that needs
|
|
11
|
+
* `shell: true` on Windows, and under a shell an argv prompt would be split on
|
|
12
|
+
* spaces, so its prompt goes over stdin. `agy` is a native binary that reads
|
|
13
|
+
* the prompt from argv and needs no shell (argv is passed literally, so no
|
|
14
|
+
* quoting hazard).
|
|
15
|
+
*/
|
|
16
|
+
export class CliAdapterBase extends EventEmitter {
|
|
17
|
+
cwd = '';
|
|
18
|
+
isFirstTurn = true;
|
|
19
|
+
isProcessing = false;
|
|
20
|
+
stopped = false;
|
|
21
|
+
child = null;
|
|
22
|
+
/** Spawn through a shell (needed to resolve a `.cmd` shim on Windows). */
|
|
23
|
+
useShell = false;
|
|
24
|
+
/** Pipe the prompt to stdin (true) vs. include it in argv via buildArgs (false). */
|
|
25
|
+
promptViaStdin = false;
|
|
26
|
+
/**
|
|
27
|
+
* Args to retry with when the first attempt of a turn exits non-zero with no
|
|
28
|
+
* output (e.g. resuming a purged session). Return null for no fallback.
|
|
29
|
+
*/
|
|
30
|
+
fallbackArgs() {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
/** Message emitted as a `system` event when the fallback kicks in. */
|
|
34
|
+
fallbackMessage = 'Retrying…';
|
|
35
|
+
/** Called once per turn after it settles; `succeeded` if it produced output or exited 0. */
|
|
36
|
+
onTurnComplete(_succeeded) { }
|
|
37
|
+
async start(cwd) {
|
|
38
|
+
this.cwd = cwd;
|
|
39
|
+
this.isFirstTurn = true;
|
|
40
|
+
}
|
|
41
|
+
async send(data) {
|
|
42
|
+
if (this.isProcessing)
|
|
43
|
+
return;
|
|
44
|
+
this.isProcessing = true;
|
|
45
|
+
this.stopped = false;
|
|
46
|
+
const args = this.buildArgs(this.isFirstTurn, data);
|
|
47
|
+
this.isFirstTurn = false;
|
|
48
|
+
this.runTurn(args, data, true);
|
|
49
|
+
}
|
|
50
|
+
runTurn(args, data, allowFallback) {
|
|
51
|
+
const cliProcess = spawn(this.binary, args, {
|
|
52
|
+
cwd: this.cwd,
|
|
53
|
+
shell: this.useShell,
|
|
54
|
+
env: { ...process.env, FORCE_COLOR: '0' } // Strip colors for easier parsing
|
|
55
|
+
});
|
|
56
|
+
this.child = cliProcess;
|
|
57
|
+
if (this.promptViaStdin) {
|
|
58
|
+
cliProcess.stdin?.write(data);
|
|
59
|
+
cliProcess.stdin?.end();
|
|
60
|
+
}
|
|
61
|
+
let producedOutput = false;
|
|
62
|
+
let stderrTail = '';
|
|
63
|
+
cliProcess.stdout?.on('data', (chunk) => {
|
|
64
|
+
producedOutput = true;
|
|
65
|
+
this.emit('data', chunk.toString());
|
|
66
|
+
});
|
|
67
|
+
// CLIs print warnings and progress on stderr; only keep it for diagnostics.
|
|
68
|
+
cliProcess.stderr?.on('data', (chunk) => {
|
|
69
|
+
const text = chunk.toString();
|
|
70
|
+
stderrTail = (stderrTail + text).slice(-2000);
|
|
71
|
+
console.warn(`[${this.label} stderr]`, text.trimEnd());
|
|
72
|
+
});
|
|
73
|
+
cliProcess.on('error', (err) => {
|
|
74
|
+
this.isProcessing = false;
|
|
75
|
+
this.child = null;
|
|
76
|
+
this.emit('error', new Error(`Failed to start ${this.label}: ${err.message}`));
|
|
77
|
+
this.emit('idle');
|
|
78
|
+
});
|
|
79
|
+
cliProcess.on('close', (code) => {
|
|
80
|
+
this.child = null;
|
|
81
|
+
if (!this.stopped && code !== 0 && !producedOutput && allowFallback) {
|
|
82
|
+
const fb = this.fallbackArgs();
|
|
83
|
+
if (fb) {
|
|
84
|
+
this.emit('system', this.fallbackMessage);
|
|
85
|
+
this.runTurn(fb, data, false);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
this.isProcessing = false;
|
|
90
|
+
this.onTurnComplete(code === 0 || producedOutput);
|
|
91
|
+
// A non-zero exit after an intentional stop is expected, not an error.
|
|
92
|
+
if (!this.stopped && code !== 0 && !producedOutput) {
|
|
93
|
+
this.emit('error', new Error(`${this.label} exited with code ${code}${stderrTail ? `: ${stderrTail.trim()}` : ''}`));
|
|
94
|
+
}
|
|
95
|
+
// Turn separation is the GUI's concern — do not inject stray output here,
|
|
96
|
+
// or a failed turn would spawn an empty bubble on the client.
|
|
97
|
+
this.emit('idle');
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
stop() {
|
|
101
|
+
this.stopped = true;
|
|
102
|
+
killTree(this.child);
|
|
103
|
+
this.child = null;
|
|
104
|
+
this.isProcessing = false;
|
|
105
|
+
this.emit('close', 0);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// With shell:true on Windows, child.kill() only kills the shell wrapper,
|
|
109
|
+
// so take the whole tree down via taskkill.
|
|
110
|
+
export function killTree(child) {
|
|
111
|
+
if (!child || child.killed || child.exitCode !== null || !child.pid)
|
|
112
|
+
return;
|
|
113
|
+
if (process.platform === 'win32') {
|
|
114
|
+
spawn('taskkill', ['/pid', String(child.pid), '/T', '/F'], { stdio: 'ignore' });
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
child.kill('SIGTERM');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=CliAdapterBase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CliAdapterBase.js","sourceRoot":"","sources":["../../src/agent/CliAdapterBase.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NativeAgentBase } from './NativeAgentBase.js';
|
|
2
|
+
export declare class NativeAgent extends NativeAgentBase {
|
|
3
|
+
protected readonly label = "NativeAgent";
|
|
4
|
+
private openai;
|
|
5
|
+
private messages;
|
|
6
|
+
private modelName;
|
|
7
|
+
constructor();
|
|
8
|
+
private client;
|
|
9
|
+
protected configError(): string | null;
|
|
10
|
+
protected resetHistory(): void;
|
|
11
|
+
protected runLoop(userInput: string, signal: AbortSignal): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=NativeAgent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NativeAgent.d.ts","sourceRoot":"","sources":["../../src/agent/NativeAgent.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AASvD,qBAAa,WAAY,SAAQ,eAAe;IAC9C,SAAS,CAAC,QAAQ,CAAC,KAAK,iBAAiB;IACzC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,SAAS,CAAS;;IAS1B,OAAO,CAAC,MAAM;IAId,SAAS,CAAC,WAAW,IAAI,MAAM,GAAG,IAAI;IAItC,SAAS,CAAC,YAAY;cAIN,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;CAiF/D"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { NativeAgentBase } from './NativeAgentBase.js';
|
|
3
|
+
import { NATIVE_TOOLS, NATIVE_STEP_LIMIT, STEP_LIMIT_NOTICE, buildSystemPrompt, executeNativeTool, } from './nativeTools.js';
|
|
4
|
+
export class NativeAgent extends NativeAgentBase {
|
|
5
|
+
label = 'NativeAgent';
|
|
6
|
+
openai = null;
|
|
7
|
+
messages = [];
|
|
8
|
+
modelName;
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
this.modelName = process.env.OPENAI_MODEL || 'gpt-4o';
|
|
12
|
+
}
|
|
13
|
+
// The OpenAI SDK throws on an empty key, so build the client lazily — after
|
|
14
|
+
// configError() has gated on the key being present.
|
|
15
|
+
client() {
|
|
16
|
+
return (this.openai ??= new OpenAI({ apiKey: process.env.OPENAI_API_KEY || '' }));
|
|
17
|
+
}
|
|
18
|
+
configError() {
|
|
19
|
+
return process.env.OPENAI_API_KEY ? null : 'OpenAI API key is not configured (set OPENAI_API_KEY).';
|
|
20
|
+
}
|
|
21
|
+
resetHistory() {
|
|
22
|
+
this.messages = [{ role: 'system', content: buildSystemPrompt(this.cwd) }];
|
|
23
|
+
}
|
|
24
|
+
async runLoop(userInput, signal) {
|
|
25
|
+
this.messages.push({ role: 'user', content: userInput });
|
|
26
|
+
const tools = NATIVE_TOOLS.map((t) => ({
|
|
27
|
+
type: 'function',
|
|
28
|
+
function: {
|
|
29
|
+
name: t.name,
|
|
30
|
+
description: t.description,
|
|
31
|
+
parameters: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: { [t.argName]: { type: 'string', description: t.argDescription } },
|
|
34
|
+
required: [t.argName],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
}));
|
|
38
|
+
let completed = false;
|
|
39
|
+
for (let step = 0; step < NATIVE_STEP_LIMIT; step++) {
|
|
40
|
+
if (signal.aborted)
|
|
41
|
+
break;
|
|
42
|
+
const stream = await this.client().chat.completions.create({
|
|
43
|
+
model: this.modelName,
|
|
44
|
+
messages: this.messages,
|
|
45
|
+
tools: tools,
|
|
46
|
+
stream: true,
|
|
47
|
+
}, { signal });
|
|
48
|
+
let content = '';
|
|
49
|
+
const toolCalls = [];
|
|
50
|
+
for await (const chunk of stream) {
|
|
51
|
+
const delta = chunk.choices[0]?.delta;
|
|
52
|
+
if (!delta)
|
|
53
|
+
continue;
|
|
54
|
+
if (delta.content) {
|
|
55
|
+
content += delta.content;
|
|
56
|
+
this.emit('data', delta.content);
|
|
57
|
+
}
|
|
58
|
+
if (delta.tool_calls) {
|
|
59
|
+
for (const toolCall of delta.tool_calls) {
|
|
60
|
+
const index = toolCall.index;
|
|
61
|
+
if (!toolCalls[index]) {
|
|
62
|
+
toolCalls[index] = {
|
|
63
|
+
id: toolCall.id,
|
|
64
|
+
type: 'function',
|
|
65
|
+
function: { name: toolCall.function?.name, arguments: '' },
|
|
66
|
+
};
|
|
67
|
+
this.emit('data', `\n\n*Running tool: ${toolCall.function?.name}*\n`);
|
|
68
|
+
}
|
|
69
|
+
if (toolCall.function?.arguments) {
|
|
70
|
+
toolCalls[index].function.arguments += toolCall.function.arguments;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
this.messages.push({ role: 'assistant', content, tool_calls: toolCalls.length > 0 ? toolCalls : undefined });
|
|
76
|
+
if (toolCalls.length === 0) {
|
|
77
|
+
completed = true;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
for (const tc of toolCalls) {
|
|
81
|
+
let result;
|
|
82
|
+
try {
|
|
83
|
+
const args = JSON.parse(tc.function.arguments || '{}');
|
|
84
|
+
result = await executeNativeTool(this.cwd, tc.function.name, args);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
result = `Error: ${err.message}`;
|
|
88
|
+
}
|
|
89
|
+
this.messages.push({ role: 'tool', tool_call_id: tc.id, name: tc.function.name, content: result });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (!completed && !signal.aborted) {
|
|
93
|
+
this.emit('data', STEP_LIMIT_NOTICE);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=NativeAgent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NativeAgent.js","sourceRoot":"","sources":["../../src/agent/NativeAgent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
/**
|
|
3
|
+
* Shared lifecycle for the SDK-backed native agents. Owns the processing guard,
|
|
4
|
+
* the abort controller, and the idle/error signalling so each provider only
|
|
5
|
+
* implements its SDK-specific streaming loop.
|
|
6
|
+
*/
|
|
7
|
+
export declare abstract class NativeAgentBase extends EventEmitter {
|
|
8
|
+
protected cwd: string;
|
|
9
|
+
protected isProcessing: boolean;
|
|
10
|
+
protected abortController: AbortController | null;
|
|
11
|
+
/** Human label used in server-side error logs. */
|
|
12
|
+
protected abstract readonly label: string;
|
|
13
|
+
/** Clear provider-specific conversation history for a new session. */
|
|
14
|
+
protected abstract resetHistory(): void;
|
|
15
|
+
/** Run one user turn's agent loop, honoring `signal` for cancellation. */
|
|
16
|
+
protected abstract runLoop(userInput: string, signal: AbortSignal): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Returns a user-facing message when the provider isn't usable (e.g. missing
|
|
19
|
+
* API key), or null when it is. Checked before each turn so a misconfigured
|
|
20
|
+
* provider gives a clear error instead of a raw SDK auth exception.
|
|
21
|
+
*/
|
|
22
|
+
protected configError(): string | null;
|
|
23
|
+
start(cwd: string): Promise<void>;
|
|
24
|
+
send(data: string): Promise<void>;
|
|
25
|
+
stop(): void;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=NativeAgentBase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NativeAgentBase.d.ts","sourceRoot":"","sources":["../../src/agent/NativeAgentBase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;GAIG;AACH,8BAAsB,eAAgB,SAAQ,YAAY;IACxD,SAAS,CAAC,GAAG,EAAE,MAAM,CAAM;IAC3B,SAAS,CAAC,YAAY,EAAE,OAAO,CAAS;IACxC,SAAS,CAAC,eAAe,EAAE,eAAe,GAAG,IAAI,CAAQ;IAEzD,kDAAkD;IAClD,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAE1C,sEAAsE;IACtE,SAAS,CAAC,QAAQ,CAAC,YAAY,IAAI,IAAI;IAEvC,0EAA0E;IAC1E,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAEjF;;;;OAIG;IACH,SAAS,CAAC,WAAW,IAAI,MAAM,GAAG,IAAI;IAIzB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BvC,IAAI,IAAI,IAAI;CAKpB"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
/**
|
|
3
|
+
* Shared lifecycle for the SDK-backed native agents. Owns the processing guard,
|
|
4
|
+
* the abort controller, and the idle/error signalling so each provider only
|
|
5
|
+
* implements its SDK-specific streaming loop.
|
|
6
|
+
*/
|
|
7
|
+
export class NativeAgentBase extends EventEmitter {
|
|
8
|
+
cwd = '';
|
|
9
|
+
isProcessing = false;
|
|
10
|
+
abortController = null;
|
|
11
|
+
/**
|
|
12
|
+
* Returns a user-facing message when the provider isn't usable (e.g. missing
|
|
13
|
+
* API key), or null when it is. Checked before each turn so a misconfigured
|
|
14
|
+
* provider gives a clear error instead of a raw SDK auth exception.
|
|
15
|
+
*/
|
|
16
|
+
configError() {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
async start(cwd) {
|
|
20
|
+
this.cwd = cwd;
|
|
21
|
+
this.resetHistory();
|
|
22
|
+
}
|
|
23
|
+
async send(data) {
|
|
24
|
+
if (this.isProcessing)
|
|
25
|
+
return;
|
|
26
|
+
const cfgError = this.configError();
|
|
27
|
+
if (cfgError) {
|
|
28
|
+
this.emit('error', new Error(cfgError));
|
|
29
|
+
this.emit('idle');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this.isProcessing = true;
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
this.abortController = controller;
|
|
35
|
+
try {
|
|
36
|
+
await this.runLoop(data, controller.signal);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
// Any error surfacing after an intentional Stop is the abort, not a
|
|
40
|
+
// failure — the SDKs throw APIUserAbortError (name !== 'AbortError'),
|
|
41
|
+
// so check the signal rather than the error name.
|
|
42
|
+
if (!controller.signal.aborted) {
|
|
43
|
+
console.error(`${this.label} error:`, err);
|
|
44
|
+
this.emit('error', err);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
this.isProcessing = false;
|
|
49
|
+
this.emit('idle');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
stop() {
|
|
53
|
+
this.abortController?.abort();
|
|
54
|
+
this.abortController = null;
|
|
55
|
+
this.emit('close', 0);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=NativeAgentBase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NativeAgentBase.js","sourceRoot":"","sources":["../../src/agent/NativeAgentBase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;GAIG;AACH,MAAM,OAAgB,eAAgB,SAAQ,YAAY;IAC9C,GAAG,GAAW,EAAE,CAAC;IACjB,YAAY,GAAY,KAAK,CAAC;IAC9B,eAAe,GAA2B,IAAI,CAAC;IAWzD;;;;OAIG;IACO,WAAW;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAEM,KAAK,CAAC,KAAK,CAAC,GAAW;QAC5B,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,IAAY;QAC5B,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO;QAE9B,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACpC,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;YACxC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAClB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,IAAI,CAAC,eAAe,GAAG,UAAU,CAAC;QAClC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,oEAAoE;YACpE,sEAAsE;YACtE,kDAAkD;YAClD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBAC/B,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,SAAS,EAAE,GAAG,CAAC,CAAC;gBAC3C,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAEM,IAAI;QACT,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,CAAC;QAC9B,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACxB,CAAC;CACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NativeAgentBase } from './NativeAgentBase.js';
|
|
2
|
+
export declare class NativeClaudeAgent extends NativeAgentBase {
|
|
3
|
+
protected readonly label = "NativeClaudeAgent";
|
|
4
|
+
private anthropic;
|
|
5
|
+
private messages;
|
|
6
|
+
private modelName;
|
|
7
|
+
constructor();
|
|
8
|
+
private client;
|
|
9
|
+
protected configError(): string | null;
|
|
10
|
+
protected resetHistory(): void;
|
|
11
|
+
protected runLoop(userInput: string, signal: AbortSignal): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=NativeClaudeAgent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NativeClaudeAgent.d.ts","sourceRoot":"","sources":["../../src/agent/NativeClaudeAgent.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AASvD,qBAAa,iBAAkB,SAAQ,eAAe;IACpD,SAAS,CAAC,QAAQ,CAAC,KAAK,uBAAuB;IAC/C,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,SAAS,CAAS;;IAO1B,OAAO,CAAC,MAAM;IAId,SAAS,CAAC,WAAW,IAAI,MAAM,GAAG,IAAI;IAItC,SAAS,CAAC,YAAY;cAIN,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;CAiG/D"}
|