@automagik/genie 4.260331.5 → 4.260331.7

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.
@@ -3,13 +3,12 @@
3
3
  *
4
4
  * Scans {workspace}/agents/ for directories containing AGENTS.md (the discovery marker).
5
5
  * For each found agent, reads the git remote to derive repo_path, then registers or
6
- * updates the entry in the app_store.
6
+ * updates the entry in the agent directory.
7
7
  *
8
8
  * Handles lifecycle:
9
- * - New agent dir → register in app_store + agents table
9
+ * - New agent dir → register in agent directory
10
10
  * - Existing agent with stale data → update
11
- * - Archived agent reappearingreactivate with full backfill
12
- * - Missing agent dir → archive (never delete — preserves history)
11
+ * - Missing agent dirremove
13
12
  *
14
13
  * Used by:
15
14
  * - `genie serve` (startup sync + file watcher)
@@ -20,13 +19,7 @@
20
19
  import { execSync } from 'node:child_process';
21
20
  import { existsSync, watch as fsWatch, readdirSync, realpathSync } from 'node:fs';
22
21
  import { join } from 'node:path';
23
- import {
24
- getItemFromStore,
25
- listItemsFromStore,
26
- regenerateAgentCache,
27
- registerItemInStore,
28
- updateItemInStore,
29
- } from './agent-cache.js';
22
+ import * as directory from './agent-directory.js';
30
23
 
31
24
  // ============================================================================
32
25
  // Types
@@ -128,98 +121,6 @@ function discoverSingleAgent(workspaceRoot: string, agentName: string): AgentInf
128
121
  };
129
122
  }
130
123
 
131
- // ============================================================================
132
- // Archive / Reactivation helpers
133
- // ============================================================================
134
-
135
- /** Archive an agent in both agents table and app_store. Never deletes rows. */
136
- async function archiveAgent(name: string): Promise<boolean> {
137
- let archived = false;
138
- try {
139
- const { getConnection } = await import('./db.js');
140
- const sql = await getConnection();
141
- // Archive in agents table (set state='archived')
142
- const result = await sql`
143
- UPDATE agents SET state = 'archived', updated_at = now()
144
- WHERE (custom_name = ${name} OR role = ${name})
145
- AND (state IS NULL OR state != 'archived')
146
- `;
147
- if (result.count > 0) archived = true;
148
- } catch {
149
- // DB may not be available
150
- }
151
-
152
- // Archive in app_store (set approval_status='archived')
153
- try {
154
- const existing = await getItemFromStore(name).catch(() => null);
155
- if (existing && existing.approval_status !== 'archived') {
156
- await updateItemInStore(name, {
157
- manifest: {
158
- ...(existing.manifest as Record<string, unknown>),
159
- archived: true,
160
- archivedAt: new Date().toISOString(),
161
- },
162
- });
163
- archived = true;
164
- }
165
- } catch {
166
- // Best-effort
167
- }
168
-
169
- return archived;
170
- }
171
-
172
- /** Reactivate an archived agent with full backfill. */
173
- async function reactivateAgent(agent: AgentInfo): Promise<void> {
174
- const orgRepo = agent.repoUrl ? extractOrgRepo(agent.repoUrl) : null;
175
- const repoPath = orgRepo ?? agent.repoUrl ?? agent.dir;
176
-
177
- // Update app_store entry — clear archived flag, refresh paths
178
- const existing = await getItemFromStore(agent.name).catch(() => null);
179
- if (existing) {
180
- const manifest = { ...(existing.manifest as Record<string, unknown>) };
181
- manifest.archived = undefined;
182
- manifest.archivedAt = undefined;
183
- manifest.repo = repoPath;
184
- manifest.productRepo = agent.productRepo;
185
- manifest.source = 'auto-sync';
186
- await updateItemInStore(agent.name, {
187
- installPath: agent.dir,
188
- gitUrl: agent.repoUrl ?? undefined,
189
- manifest,
190
- });
191
- }
192
-
193
- // Reactivate in agents table
194
- try {
195
- const { getConnection } = await import('./db.js');
196
- const sql = await getConnection();
197
- await sql`
198
- UPDATE agents SET state = 'idle', repo_path = ${repoPath}, updated_at = now()
199
- WHERE (custom_name = ${agent.name} OR role = ${agent.name})
200
- AND state = 'archived'
201
- `;
202
- } catch {
203
- // DB may not be available
204
- }
205
-
206
- // Trigger session backfill for conversation history recovery
207
- await triggerSessionBackfill(agent).catch(() => {});
208
- }
209
-
210
- /** Trigger session backfill for a reactivated agent. */
211
- async function triggerSessionBackfill(_agent: AgentInfo): Promise<void> {
212
- try {
213
- const { getConnection, isAvailable } = await import('./db.js');
214
- if (!(await isAvailable())) return;
215
- const sql = await getConnection();
216
- const { startBackfill } = await import('./session-backfill.js');
217
- await startBackfill(sql);
218
- } catch {
219
- // Best-effort — backfill may not be ready
220
- }
221
- }
222
-
223
124
  // ============================================================================
224
125
  // Sync
225
126
  // ============================================================================
@@ -227,9 +128,8 @@ async function triggerSessionBackfill(_agent: AgentInfo): Promise<void> {
227
128
  /**
228
129
  * Sync all agents from {workspaceRoot}/agents/ into the directory.
229
130
  * - New agents → registered
230
- * - Existing agents with stale repo_path → updated
231
- * - Archived agents reappearing reactivated with backfill
232
- * - Agents in store whose dirs are gone → archived
131
+ * - Existing agents with stale data → updated
132
+ * - Agents whose dirs are gone removed
233
133
  *
234
134
  * Idempotent — safe to run repeatedly.
235
135
  */
@@ -249,15 +149,8 @@ export async function syncAgentDirectory(workspaceRoot: string): Promise<SyncRes
249
149
  }
250
150
  }
251
151
 
252
- // Archive agents in store whose dirs no longer exist
253
- await archiveMissingAgents(workspaceRoot, discoveredNames, result);
254
-
255
- // Regenerate cache after mutations
256
- const hasMutations =
257
- result.registered.length + result.updated.length + result.archived.length + result.reactivated.length;
258
- if (hasMutations > 0) {
259
- await regenerateAgentCache().catch(() => {});
260
- }
152
+ // Remove agents whose dirs no longer exist
153
+ await removeMissingAgents(discoveredNames, result);
261
154
 
262
155
  return result;
263
156
  }
@@ -267,58 +160,30 @@ export function printSyncResult(result: SyncResult): void {
267
160
  if (result.registered.length > 0) console.log(` Registered: ${result.registered.join(', ')}`);
268
161
  if (result.updated.length > 0) console.log(` Updated: ${result.updated.join(', ')}`);
269
162
  if (result.reactivated.length > 0) console.log(` Reactivated: ${result.reactivated.join(', ')}`);
270
- if (result.archived.length > 0) console.log(` Archived: ${result.archived.join(', ')}`);
163
+ if (result.archived.length > 0) console.log(` Removed: ${result.archived.join(', ')}`);
271
164
  if (result.unchanged.length > 0) console.log(` Unchanged: ${result.unchanged.join(', ')}`);
272
165
  for (const err of result.errors) {
273
166
  console.error(` Error (${err.name}): ${err.error}`);
274
167
  }
275
168
  const total = result.registered.length + result.updated.length + result.unchanged.length + result.reactivated.length;
276
- console.log(`\nSync complete: ${total} active agent(s), ${result.archived.length} archived.`);
169
+ console.log(`\nSync complete: ${total} active agent(s), ${result.archived.length} removed.`);
277
170
  }
278
171
 
279
- /** Archive app_store agents whose directories no longer exist on disk. */
280
- async function archiveMissingAgents(
281
- workspaceRoot: string,
282
- discoveredNames: Set<string>,
283
- result: SyncResult,
284
- ): Promise<void> {
172
+ /** Remove directory entries whose agent dirs no longer exist on disk. */
173
+ async function removeMissingAgents(discoveredNames: Set<string>, result: SyncResult): Promise<void> {
285
174
  try {
286
- const storeItems = await listItemsFromStore('agent');
287
- const agentsDir = join(workspaceRoot, 'agents');
288
-
289
- for (const item of storeItems) {
290
- if (discoveredNames.has(item.name)) continue;
291
- const manifest = (item.manifest ?? {}) as Record<string, unknown>;
292
- if (manifest.archived) continue; // already archived
293
- if (manifest.source !== 'auto-sync') continue; // only archive auto-synced agents
294
-
295
- // Verify the agent was from this workspace
296
- if (item.install_path && !item.install_path.startsWith(agentsDir)) continue;
175
+ const entries = await directory.ls();
176
+ for (const entry of entries) {
177
+ if (discoveredNames.has(entry.name)) continue;
178
+ if (entry.scope === 'built-in') continue;
179
+ if (!entry.dir || !entry.dir.includes('/agents/')) continue; // only remove auto-synced
297
180
 
298
- const archived = await archiveAgent(item.name);
299
- if (archived) result.archived.push(item.name);
181
+ const removed = await directory.rm(entry.name);
182
+ if (removed) result.archived.push(entry.name);
300
183
  }
301
184
  } catch {
302
- // Best-effort — don't block sync on archive failures
303
- }
304
- }
305
-
306
- /** Sync a single agent by name from the workspace (used by file watcher). */
307
- async function syncSingleAgentByName(workspaceRoot: string, agentName: string): Promise<string> {
308
- const agent = discoverSingleAgent(workspaceRoot, agentName);
309
- if (!agent) return 'not-found';
310
-
311
- const result: SyncResult = { registered: [], updated: [], unchanged: [], archived: [], reactivated: [], errors: [] };
312
- await syncSingleAgent(agent, result);
313
-
314
- if (result.registered.length > 0 || result.updated.length > 0 || result.reactivated.length > 0) {
315
- await regenerateAgentCache().catch(() => {});
185
+ // Best-effort
316
186
  }
317
-
318
- if (result.reactivated.length > 0) return 'reactivated';
319
- if (result.registered.length > 0) return 'registered';
320
- if (result.updated.length > 0) return 'updated';
321
- return 'unchanged';
322
187
  }
323
188
 
324
189
  /** Core sync logic for a single agent. */
@@ -326,39 +191,26 @@ async function syncSingleAgent(agent: AgentInfo, result: SyncResult): Promise<vo
326
191
  const orgRepo = agent.repoUrl ? extractOrgRepo(agent.repoUrl) : null;
327
192
  const repoPath = orgRepo ?? agent.repoUrl ?? agent.dir;
328
193
 
329
- const existing = await getItemFromStore(agent.name).catch(() => null);
194
+ const existing = await directory.get(agent.name);
330
195
 
331
196
  if (!existing) {
332
- await registerItemInStore({
197
+ await directory.add({
333
198
  name: agent.name,
334
- itemType: 'agent',
335
- installPath: agent.dir,
336
- gitUrl: agent.repoUrl ?? undefined,
337
- manifest: { promptMode: 'append', repo: repoPath, productRepo: agent.productRepo, source: 'auto-sync' },
199
+ dir: agent.dir,
200
+ repo: repoPath,
201
+ promptMode: 'append',
338
202
  });
339
203
  result.registered.push(agent.name);
340
204
  return;
341
205
  }
342
206
 
343
- // Check if this is a reactivation (was archived)
344
- const manifest = (existing.manifest ?? {}) as Record<string, unknown>;
345
- if (manifest.archived) {
346
- await reactivateAgent(agent);
347
- result.reactivated.push(agent.name);
348
- return;
349
- }
350
-
351
207
  // Check if update needed
352
- const needsUpdate =
353
- (manifest.repo as string) !== repoPath ||
354
- existing.install_path !== agent.dir ||
355
- (agent.productRepo && manifest.productRepo !== agent.productRepo);
208
+ const needsUpdate = existing.repo !== repoPath || existing.dir !== agent.dir;
356
209
 
357
210
  if (needsUpdate) {
358
- await updateItemInStore(agent.name, {
359
- installPath: agent.dir,
360
- gitUrl: agent.repoUrl ?? undefined,
361
- manifest: { ...manifest, repo: repoPath, productRepo: agent.productRepo, source: 'auto-sync' },
211
+ await directory.edit(agent.name, {
212
+ dir: agent.dir,
213
+ repo: repoPath,
362
214
  });
363
215
  result.updated.push(agent.name);
364
216
  } else {
@@ -374,21 +226,17 @@ interface AgentWatcher {
374
226
  close: () => void;
375
227
  }
376
228
 
377
- /** Process a single watched agent change register, update, archive, or reactivate. */
378
- async function processWatchedAgent(workspaceRoot: string, agentsDir: string, name: string): Promise<string | null> {
379
- const agentDir = join(agentsDir, name);
380
- if (existsSync(agentDir) && existsSync(join(agentDir, 'AGENTS.md'))) {
381
- const action = await syncSingleAgentByName(workspaceRoot, name);
382
- return action !== 'unchanged' && action !== 'not-found' ? action : null;
383
- }
384
- if (!existsSync(agentDir)) {
385
- const archived = await archiveAgent(name);
386
- if (archived) {
387
- await regenerateAgentCache().catch(() => {});
388
- return 'archived';
389
- }
390
- }
391
- return null;
229
+ /** Sync a single agent by name from the workspace (used by file watcher). */
230
+ async function syncSingleAgentByName(workspaceRoot: string, agentName: string): Promise<string> {
231
+ const agent = discoverSingleAgent(workspaceRoot, agentName);
232
+ if (!agent) return 'not-found';
233
+
234
+ const result: SyncResult = { registered: [], updated: [], unchanged: [], archived: [], reactivated: [], errors: [] };
235
+ await syncSingleAgent(agent, result);
236
+
237
+ if (result.registered.length > 0) return 'registered';
238
+ if (result.updated.length > 0) return 'updated';
239
+ return 'unchanged';
392
240
  }
393
241
 
394
242
  /**
@@ -396,8 +244,7 @@ async function processWatchedAgent(workspaceRoot: string, agentsDir: string, nam
396
244
  * Debounces changes with a 2s window.
397
245
  *
398
246
  * - New directory with AGENTS.md → auto-register
399
- * - Archived agent reappearing reactivate with backfill
400
- * - Removed directory → archive (never delete)
247
+ * - Removed directoryremove from directory
401
248
  */
402
249
  export function watchAgentDirectory(
403
250
  workspaceRoot: string,
@@ -440,3 +287,17 @@ export function watchAgentDirectory(
440
287
  },
441
288
  };
442
289
  }
290
+
291
+ /** Process a single watched agent change. */
292
+ async function processWatchedAgent(workspaceRoot: string, agentsDir: string, name: string): Promise<string | null> {
293
+ const agentDir = join(agentsDir, name);
294
+ if (existsSync(agentDir) && existsSync(join(agentDir, 'AGENTS.md'))) {
295
+ const action = await syncSingleAgentByName(workspaceRoot, name);
296
+ return action !== 'unchanged' && action !== 'not-found' ? action : null;
297
+ }
298
+ if (!existsSync(agentDir)) {
299
+ const removed = await directory.rm(name);
300
+ if (removed) return 'removed';
301
+ }
302
+ return null;
303
+ }
@@ -18,16 +18,7 @@ export interface ExportDocument {
18
18
  data: Record<string, unknown[]>;
19
19
  }
20
20
 
21
- export type ExportGroup =
22
- | 'boards'
23
- | 'tasks'
24
- | 'tags'
25
- | 'projects'
26
- | 'schedules'
27
- | 'agents'
28
- | 'apps'
29
- | 'comms'
30
- | 'config';
21
+ export type ExportGroup = 'boards' | 'tasks' | 'tags' | 'projects' | 'schedules' | 'agents' | 'comms' | 'config';
31
22
 
32
23
  /** Tables belonging to each export group */
33
24
  export const GROUP_TABLES: Record<ExportGroup, string[]> = {
@@ -37,7 +28,6 @@ export const GROUP_TABLES: Record<ExportGroup, string[]> = {
37
28
  projects: ['projects'],
38
29
  schedules: ['schedules'],
39
30
  agents: ['agents', 'agent_templates', 'agent_checkpoints'],
40
- apps: ['app_store', 'installed_apps', 'app_versions'],
41
31
  comms: ['conversations', 'conversation_members', 'messages', 'mailbox', 'team_chat', 'notification_preferences'],
42
32
  config: ['os_config', 'instances', 'warm_pool', 'golden_images'],
43
33
  };
@@ -49,7 +39,6 @@ export const ALL_GROUPS: ExportGroup[] = [
49
39
  'projects',
50
40
  'schedules',
51
41
  'agents',
52
- 'apps',
53
42
  'comms',
54
43
  'config',
55
44
  ];
@@ -21,23 +21,13 @@ const IMPORT_LEVELS: string[][] = [
21
21
  'task_types',
22
22
  'notification_preferences',
23
23
  // Optional (KhalOS)
24
- 'app_store',
25
24
  'os_config',
26
25
  'golden_images',
27
26
  'warm_pool',
28
27
  'instances',
29
28
  ],
30
29
  // Level 1: Depend on Level 0
31
- [
32
- 'triggers',
33
- 'boards',
34
- 'board_templates',
35
- 'agents',
36
- 'conversations',
37
- // Optional (KhalOS)
38
- 'installed_apps',
39
- 'app_versions',
40
- ],
30
+ ['triggers', 'boards', 'board_templates', 'agents', 'conversations'],
41
31
  // Level 2: Depend on Level 1
42
32
  ['tasks', 'runs', 'messages', 'conversation_members', 'mailbox', 'team_chat'],
43
33
  // Level 3: Depend on Level 2
@@ -4,7 +4,6 @@
4
4
  */
5
5
 
6
6
  import type { Command } from 'commander';
7
- import { type StoreRow, listItemsFromStore, migrateAgentDirectory } from '../../lib/agent-cache.js';
8
7
  import * as directory from '../../lib/agent-directory.js';
9
8
  import { printSyncResult, syncAgentDirectory } from '../../lib/agent-sync.js';
10
9
  import { ALL_BUILTINS } from '../../lib/builtin-agents.js';
@@ -82,42 +81,8 @@ function printBuiltinAgentsTable(): void {
82
81
  console.log('');
83
82
  }
84
83
 
85
- function normalizeRoles(roles?: string[]): string[] | undefined {
86
- if (!roles) return undefined;
87
- return roles
88
- .flatMap((r) => r.split(','))
89
- .map((r) => r.trim())
90
- .filter(Boolean);
91
- }
92
-
93
- async function listEntries(json?: boolean, includeBuiltins?: boolean, includeArchived?: boolean): Promise<void> {
94
- await migrateAgentDirectory().catch(() => {});
95
-
96
- let entries: directory.ScopedDirectoryEntry[];
97
- try {
98
- const storeItems = await listItemsFromStore('agent');
99
- entries = storeItems
100
- .filter((item: StoreRow) => {
101
- if (includeArchived) return true;
102
- const manifest = (item.manifest ?? {}) as Record<string, unknown>;
103
- return !manifest.archived;
104
- })
105
- .map((item: StoreRow) => {
106
- const manifest = (item.manifest ?? {}) as Record<string, unknown>;
107
- return {
108
- name: item.name,
109
- dir: (item.install_path as string) ?? '',
110
- repo: (manifest.repo as string) ?? '',
111
- promptMode: ((manifest.promptMode as string) ?? 'append') as directory.PromptMode,
112
- model: manifest.model as string | undefined,
113
- roles: normalizeRoles(manifest.roles as string[] | undefined),
114
- registeredAt: item.installed_at as string,
115
- scope: (manifest.archived ? 'archived' : 'global') as directory.DirectoryScope,
116
- };
117
- });
118
- } catch {
119
- entries = await directory.ls();
120
- }
84
+ async function listEntries(json?: boolean, includeBuiltins?: boolean, _includeArchived?: boolean): Promise<void> {
85
+ const entries = await directory.ls();
121
86
 
122
87
  if (json) {
123
88
  const result: Record<string, unknown>[] = entries.map((e) => ({ ...e, builtin: false }));
@@ -37,4 +37,10 @@ export function registerAgentCommands(program: Command): void {
37
37
  registerAgentBrief(agent);
38
38
  registerAgentLog(agent);
39
39
  registerAgentSend(agent);
40
+
41
+ agent.on('command:*', (operands: string[]) => {
42
+ const cmd = operands[0];
43
+ const available = agent.commands.map((c) => c.name()).join(', ');
44
+ agent.error(`Unknown agent command '${cmd}'. Available: ${available}`);
45
+ });
40
46
  }
@@ -12,28 +12,16 @@
12
12
  * Commands:
13
13
  * genie agent register <name> — Register agent locally + auto-register in Omni
14
14
  *
15
- * Storage: Primary source is `app_store` table (item_type='agent').
16
- * Legacy `agents` table kept for backward compat with spawn.
17
- * JSON cache (~/.genie/agent-directory.json) regenerated after every mutation.
15
+ * Storage: agent-directory.json is the source of truth for registered agents.
18
16
  */
19
17
 
20
18
  import { resolve as resolvePath } from 'node:path';
21
19
  import type { Command } from 'commander';
22
- import {
23
- type StoreRow,
24
- listItemsFromStore,
25
- migrateAgentDirectory,
26
- regenerateAgentCache,
27
- registerItemInStore,
28
- removeItemFromStore,
29
- updateItemInStore,
30
- } from '../lib/agent-cache.js';
31
20
  import * as directory from '../lib/agent-directory.js';
32
21
  import { printSyncResult, syncAgentDirectory } from '../lib/agent-sync.js';
33
22
  import { getActor, recordAuditEvent } from '../lib/audit.js';
34
23
  import { ALL_BUILTINS } from '../lib/builtin-agents.js';
35
24
  import { contractPath } from '../lib/genie-config.js';
36
- import { findOmniAgent, registerAgentInOmni, resolveOmniApiUrl } from '../lib/omni-registration.js';
37
25
 
38
26
  export function registerDirNamespace(program: Command): void {
39
27
  const dir = program.command('dir').description('Agent directory management');
@@ -66,9 +54,6 @@ export function registerDirNamespace(program: Command): void {
66
54
  .action(async (name: string, options: { global?: boolean }) => {
67
55
  try {
68
56
  const removed = await directory.rm(name, { global: options.global });
69
- // Also remove from app_store
70
- await removeItemFromStore(name).catch(() => {});
71
- await regenerateAgentCache();
72
57
  recordAuditEvent('item', name, 'item_removed', getActor(), { type: 'agent', source: 'dir_rm' }).catch(() => {});
73
58
 
74
59
  if (removed) {
@@ -165,18 +150,6 @@ async function handleDirAdd(name: string, options: DirAddOptions): Promise<void>
165
150
  { global: options.global },
166
151
  );
167
152
 
168
- // Also register in app_store (primary source of truth)
169
- try {
170
- await registerItemInStore({
171
- name,
172
- itemType: 'agent',
173
- installPath: resolvedDir,
174
- manifest: { promptMode, model: options.model, roles: normalizeRoles(options.roles), repo: options.repo },
175
- });
176
- } catch {
177
- // Best-effort — legacy agents table is still the spawn path
178
- }
179
- await regenerateAgentCache();
180
153
  recordAuditEvent('item', name, 'item_registered', getActor(), { type: 'agent', source: 'dir_add' }).catch(() => {});
181
154
 
182
155
  const scope = options.global ? 'global' : 'project';
@@ -208,16 +181,6 @@ async function handleEdit(name: string, options: EditOptions): Promise<void> {
208
181
 
209
182
  const entry = await directory.edit(name, updates, { global: options.global });
210
183
 
211
- // Also update app_store
212
- try {
213
- await updateItemInStore(name, {
214
- installPath: updates.dir,
215
- manifest: { promptMode: updates.promptMode, model: updates.model, roles: updates.roles, repo: updates.repo },
216
- });
217
- } catch {
218
- // Best-effort
219
- }
220
- await regenerateAgentCache();
221
184
  recordAuditEvent('item', name, 'item_updated', getActor(), { type: 'agent', source: 'dir_edit' }).catch(() => {});
222
185
 
223
186
  const scope = options.global ? 'global' : 'project';
@@ -280,37 +243,8 @@ async function showEntry(name: string, json?: boolean): Promise<void> {
280
243
  console.log('');
281
244
  }
282
245
 
283
- async function listEntries(json?: boolean, includeBuiltins?: boolean, includeArchived?: boolean): Promise<void> {
284
- // One-time migration from legacy JSON → DB (idempotent, best-effort)
285
- await migrateAgentDirectory().catch(() => {});
286
-
287
- // Try app_store first (primary), fall back to legacy agents table
288
- let entries: directory.ScopedDirectoryEntry[];
289
- try {
290
- const storeItems = await listItemsFromStore('agent');
291
- entries = storeItems
292
- .filter((item: StoreRow) => {
293
- if (includeArchived) return true;
294
- const manifest = (item.manifest ?? {}) as Record<string, unknown>;
295
- return !manifest.archived;
296
- })
297
- .map((item: StoreRow) => {
298
- const manifest = (item.manifest ?? {}) as Record<string, unknown>;
299
- return {
300
- name: item.name,
301
- dir: (item.install_path as string) ?? '',
302
- repo: (manifest.repo as string) ?? '',
303
- promptMode: ((manifest.promptMode as string) ?? 'append') as directory.PromptMode,
304
- model: manifest.model as string | undefined,
305
- roles: normalizeRoles(manifest.roles as string[] | undefined),
306
- registeredAt: item.installed_at as string,
307
- scope: (manifest.archived ? 'archived' : 'global') as directory.DirectoryScope,
308
- };
309
- });
310
- } catch {
311
- // Fallback to legacy
312
- entries = await directory.ls();
313
- }
246
+ async function listEntries(json?: boolean, includeBuiltins?: boolean, _includeArchived?: boolean): Promise<void> {
247
+ const entries = await directory.ls();
314
248
 
315
249
  if (json) {
316
250
  listEntriesJson(entries, includeBuiltins);
@@ -420,94 +354,3 @@ function printBuiltinsTable(): void {
420
354
  }
421
355
  console.log('');
422
356
  }
423
-
424
- // ============================================================================
425
- // Agent namespace — genie agent register
426
- // ============================================================================
427
-
428
- interface RegisterOptions {
429
- dir: string;
430
- repo?: string;
431
- promptMode: string;
432
- model?: string;
433
- roles?: string[];
434
- global?: boolean;
435
- skipOmni?: boolean;
436
- }
437
-
438
- async function handleOmniRegistration(
439
- name: string,
440
- options: { model?: string; roles?: string[]; global?: boolean },
441
- ): Promise<void> {
442
- const omniUrl = await resolveOmniApiUrl();
443
- if (!omniUrl) return;
444
-
445
- console.log(`\nRegistering in Omni (${omniUrl})...`);
446
-
447
- const existingId = await findOmniAgent(name);
448
- if (existingId) {
449
- console.log(` Agent already exists in Omni: ${existingId}`);
450
- await directory.edit(name, { omniAgentId: existingId }, { global: options.global });
451
- console.log(' Linked existing Omni agent to directory entry.');
452
- return;
453
- }
454
-
455
- const omniAgentId = await registerAgentInOmni(name, {
456
- model: options.model,
457
- roles: options.roles,
458
- });
459
- if (omniAgentId) {
460
- await directory.edit(name, { omniAgentId }, { global: options.global });
461
- console.log(` Omni agent created: ${omniAgentId}`);
462
- console.log(' Session isolation: per-person + per-channel');
463
- }
464
- }
465
-
466
- async function handleAgentRegister(name: string, options: RegisterOptions): Promise<void> {
467
- const promptMode = validatePromptMode(options.promptMode);
468
-
469
- const roles = normalizeRoles(options.roles);
470
- const entry = await directory.add(
471
- {
472
- name,
473
- dir: resolvePath(options.dir),
474
- repo: options.repo ? resolvePath(options.repo) : undefined,
475
- promptMode,
476
- model: options.model,
477
- roles,
478
- },
479
- { global: options.global },
480
- );
481
-
482
- const scope = options.global ? 'global' : 'project';
483
- console.log(`Agent "${entry.name}" registered (${scope}).`);
484
- printEntry(entry);
485
-
486
- if (!options.skipOmni) {
487
- await handleOmniRegistration(name, { ...options, roles });
488
- }
489
- }
490
-
491
- export function registerAgentNamespace(program: Command): void {
492
- const agent = program.command('agent').description('Agent lifecycle management');
493
-
494
- agent
495
- .command('register <name>')
496
- .description('Register an agent locally and auto-register in Omni when configured')
497
- .requiredOption('--dir <path>', 'Agent folder (CWD + AGENTS.md)')
498
- .option('--repo <path>', 'Default git repo (overridden by team)')
499
- .option('--prompt-mode <mode>', 'Prompt mode: append or system', 'append')
500
- .option('--model <model>', 'Default model (sonnet, opus, codex)')
501
- .option('--roles <roles...>', 'Built-in roles this agent can orchestrate')
502
- .option('--global', 'Write to global directory instead of project')
503
- .option('--skip-omni', 'Skip Omni auto-registration')
504
- .action(async (name: string, options: RegisterOptions) => {
505
- try {
506
- await handleAgentRegister(name, options);
507
- } catch (error) {
508
- const message = error instanceof Error ? error.message : String(error);
509
- console.error(`Error: ${message}`);
510
- process.exit(1);
511
- }
512
- });
513
- }