create-loopengine 0.1.9 → 0.1.12

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.
@@ -1,15 +1,20 @@
1
1
  // Every adapter resolves "which agent" through this — the thing that
2
- // makes agents pluggable. Defining a new agent means adding a file to
3
- // agents/ that exports `config` and `createModelCall` — nothing here or
4
- // in any adapter needs to change; discoverAgents scans that directory and
5
- // keys each entry by AgentConfig.name (not the filename) at import time.
2
+ // makes agents pluggable. Defining a new agent means adding a file (or a
3
+ // folder) to agents/ that exports `config` and `createModelCall` —
4
+ // nothing here or in any adapter needs to change; discoverAgents scans
5
+ // that directory and keys each entry by AgentConfig.name (not the
6
+ // filename) at import time.
6
7
  import { dirname, join } from 'node:path'
7
8
  import { fileURLToPath } from 'node:url'
8
- import { discoverAgents, type AgentModule } from 'loopengine'
9
+ import { discoverAgents, type AgentConfig, type AgentModule, type ModelCall } from 'loopengine'
9
10
 
10
11
  export type RegistryEntry = AgentModule
11
12
 
12
- const agentsDir = join(dirname(fileURLToPath(import.meta.url)), 'agents')
13
+ // This file lives at the project root, alongside agents/ — no '..' needed
14
+ // (unlike loopengine's own core/agent-registry.ts, which lives nested one
15
+ // level down in core/).
16
+ const projectRoot = dirname(fileURLToPath(import.meta.url))
17
+ const agentsDir = join(projectRoot, 'agents')
13
18
 
14
19
  // Top-level await: ESM guarantees an importer's own evaluation (adapters/
15
20
  // http.ts, adapters/cli.ts) waits for this module's top-level await to
@@ -25,3 +30,50 @@ export function listAgents(): string[] {
25
30
  export function getEntry(name: string): RegistryEntry | undefined {
26
31
  return entries.get(name)
27
32
  }
33
+
34
+ /** Where this registry's own discoverAgents call resolved agents/
35
+ * against — exported so a caller creating a *new* agent module (the
36
+ * Admin UI's "Create new agent" flow, via loopengine's own scaffoldAgent)
37
+ * writes it to the exact same place this registry would find it on a
38
+ * future restart, rather than re-deriving a base dir independently (e.g.
39
+ * from process.cwd(), which isn't guaranteed to match if the process is
40
+ * launched from elsewhere) and risking the two drifting apart. */
41
+ export function projectDir(): string {
42
+ return projectRoot
43
+ }
44
+
45
+ /** Adds one already-loaded agent module to the live registry in place —
46
+ * how a newly created agent becomes runnable in *this* process
47
+ * immediately, with no restart — unlike discoverAgents' own directory
48
+ * scan, which only ever runs once, at this module's own import time (see
49
+ * the top-level await above). Throws on a name collision with an
50
+ * already-registered agent, same as discoverAgents itself does for a
51
+ * collision found during its initial scan — a duplicate AgentConfig.name
52
+ * is a bug either way, not something to silently overwrite. */
53
+ export function registerAgent(agentModule: RegistryEntry): void {
54
+ const name = agentModule.config.name
55
+ if (entries.has(name)) {
56
+ throw new Error(`Duplicate agent name '${name}' — an agent with this name is already registered.`)
57
+ }
58
+ entries.set(name, agentModule)
59
+ }
60
+
61
+ /** Applies a partial update to an already-registered agent — how an edit
62
+ * made through the Admin UI takes effect in *this* running process
63
+ * immediately, the same "no restart needed" principle gateway-tools.yml/
64
+ * actauth.yml/SKILL.md edits already have. Mutates the same AgentConfig
65
+ * object every existing getEntry() caller already holds a reference to —
66
+ * via Object.assign, not a full replacement — so nothing needs to
67
+ * re-fetch it to see the change, and any field left out of `patch.config`
68
+ * (tools, rules, skillsDirs, ...) stays exactly as it was.
69
+ * `patch.createModelCall`, if given, replaces the cached one wholesale.
70
+ * Throws if `name` isn't already registered — this updates an existing
71
+ * entry, it's not a back door around registerAgent's own creation path. */
72
+ export function updateAgent(name: string, patch: { config?: Partial<AgentConfig>; createModelCall?: () => ModelCall }): void {
73
+ const entry = entries.get(name)
74
+ if (!entry) {
75
+ throw new Error(`No registered agent named '${name}' to update.`)
76
+ }
77
+ if (patch.config) Object.assign(entry.config, patch.config)
78
+ if (patch.createModelCall) entry.createModelCall = patch.createModelCall
79
+ }
@@ -9,7 +9,9 @@
9
9
  "cli": "tsx adapters/cli.ts"
10
10
  },
11
11
  "dependencies": {
12
- "loopengine": "^0.1.3"
12
+ "loopengine": "^0.1.4",
13
+ "actauth": "^0.0.12",
14
+ "skillgarden": "^0.0.5"
13
15
  },
14
16
  "devDependencies": {
15
17
  "@types/node": "^22.10.0",