@automagik/genie 4.260405.3 → 4.260405.4

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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "genie",
13
- "version": "4.260405.3",
13
+ "version": "4.260405.4",
14
14
  "source": "./plugins/genie",
15
15
  "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project."
16
16
  }
package/dist/genie.js CHANGED
@@ -515,7 +515,12 @@ ${body}`;writeFileSync8(filePath,output,"utf-8")}function serializeSdkConfig(sdk
515
515
  INSERT INTO agents (id, role, custom_name, started_at, metadata)
516
516
  VALUES (${`dir:${entry.name}`}, ${entry.name}, ${entry.name}, now(), ${sql.json(metadata)})
517
517
  ON CONFLICT (id) DO UPDATE SET metadata = ${sql.json(metadata)}
518
- `,full}async function rm3(name,_options){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));return(await(await getConnection2())`DELETE FROM agents WHERE id = ${`dir:${name}`}`).count>0}async function resolve3(name){try{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),rows=await(await getConnection2())`SELECT role, metadata FROM agents WHERE role = ${name} LIMIT 1`;if(rows.length>0){let meta=parseMetadata(rows[0].metadata);return{entry:roleToEntry(name,void 0,meta),builtin:!1}}}catch{}let builtinRole=BUILTIN_ROLES.find((r)=>r.name===name);if(builtinRole)return{entry:builtinToEntry(builtinRole),builtin:!0};let councilMember=BUILTIN_COUNCIL_MEMBERS.find((m)=>m.name===name);if(councilMember)return{entry:builtinToEntry(councilMember),builtin:!0};return null}async function findSessionByRepo(repoPath){try{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),rows=await(await getConnection2())`
518
+ `,full}async function rm3(name,_options){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));return(await(await getConnection2())`DELETE FROM agents WHERE id = ${`dir:${name}`}`).count>0}async function resolve3(name){try{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),rows=await(await getConnection2())`
519
+ SELECT role, metadata FROM agents
520
+ WHERE role = ${name}
521
+ ORDER BY (CASE WHEN id LIKE 'dir:%' THEN 0 ELSE 1 END), started_at DESC
522
+ LIMIT 1
523
+ `;if(rows.length>0){let meta=parseMetadata(rows[0].metadata);return{entry:roleToEntry(name,void 0,meta),builtin:!1}}}catch{}let builtinRole=BUILTIN_ROLES.find((r)=>r.name===name);if(builtinRole)return{entry:builtinToEntry(builtinRole),builtin:!0};let councilMember=BUILTIN_COUNCIL_MEMBERS.find((m)=>m.name===name);if(councilMember)return{entry:builtinToEntry(councilMember),builtin:!0};return null}async function findSessionByRepo(repoPath){try{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),rows=await(await getConnection2())`
519
524
  SELECT session FROM agents
520
525
  WHERE repo_path = ${repoPath}
521
526
  AND session IS NOT NULL
@@ -528,7 +533,7 @@ ${body}`;writeFileSync8(filePath,output,"utf-8")}function serializeSdkConfig(sdk
528
533
  FROM agents a
529
534
  LEFT JOIN executors e ON a.current_executor_id = e.id
530
535
  WHERE a.role IS NOT NULL
531
- ORDER BY a.role, a.started_at DESC
536
+ ORDER BY a.role, (CASE WHEN a.id LIKE 'dir:%' THEN 0 ELSE 1 END), a.started_at DESC
532
537
  `;for(let row of rows){let name=row.role;if(!seen.has(name)){let meta=parseMetadata(row.metadata),entry=roleToEntry(name,row.team,meta),repoPath=row.repo_path;if(repoPath)entry.dir=repoPath,entry.repo=repoPath;result2.push({...entry,scope:"global"}),seen.add(name)}}}catch{}return result2}async function get2(name,_options){return(await resolve3(name))?.entry??null}async function edit(name,updates,_options){if(updates.dir){if(!existsSync18(updates.dir))throw Error(`Directory does not exist: ${updates.dir}`);let agentsPath=join20(updates.dir,"AGENTS.md");if(!existsSync18(agentsPath))throw Error(`AGENTS.md not found in ${updates.dir}.`)}let existing=await get2(name);if(!existing)throw Error(`Agent "${name}" not found in directory.`);let updated=Object.assign(existing,updates),metadataPatch=buildMetadata(updated);try{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),sql=await getConnection2();if((await sql`
533
538
  UPDATE agents
534
539
  SET metadata = metadata || ${sql.json(metadataPatch)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260405.3",
3
+ "version": "4.260405.4",
4
4
  "description": "Collaborative terminal toolkit for human + AI workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie",
3
- "version": "4.260405.3",
3
+ "version": "4.260405.4",
4
4
  "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
5
5
  "author": {
6
6
  "name": "Namastex Labs"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie-plugin",
3
- "version": "4.260405.3",
3
+ "version": "4.260405.4",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",
@@ -79,6 +79,22 @@ describe.skipIf(!DB_AVAILABLE)('pg', () => {
79
79
  test('returns null for unknown name', async () => {
80
80
  expect(await directory.resolve('nonexistent-xyz')).toBeNull();
81
81
  });
82
+
83
+ test('prefers dir: row over stale runtime rows with same role', async () => {
84
+ const sql = await getConnection();
85
+ // Insert stale runtime row (empty metadata, older started_at)
86
+ await sql`INSERT INTO agents (id, pane_id, session, repo_path, state, role, started_at, last_state_change, metadata) VALUES ('stale-uuid', '%1', 's', '/tmp', 'done', 'my-dup', now() - interval '1 hour', now(), '{}')`;
87
+ // Insert another stale runtime row (empty metadata, recent started_at — would win ORDER BY started_at DESC)
88
+ await sql`INSERT INTO agents (id, pane_id, session, repo_path, state, role, started_at, last_state_change, metadata) VALUES ('recent-uuid', '%2', 's', '/tmp', 'done', 'my-dup', now(), now(), '{}')`;
89
+ // Insert directory row (has real metadata — oldest started_at, but should still win via dir: prefix)
90
+ await sql`INSERT INTO agents (id, role, custom_name, started_at, metadata) VALUES ('dir:my-dup', 'my-dup', 'my-dup', now() - interval '2 hours', '{"dir":"/some/path","model":"opus","color":"teal","description":"The real entry"}')`;
91
+
92
+ const resolved = await directory.resolve('my-dup');
93
+ expect(resolved).not.toBeNull();
94
+ expect(resolved!.entry.model).toBe('opus');
95
+ expect(resolved!.entry.color).toBe('teal');
96
+ expect(resolved!.entry.description).toBe('The real entry');
97
+ });
82
98
  });
83
99
 
84
100
  // ============================================================================
@@ -101,6 +117,18 @@ describe.skipIf(!DB_AVAILABLE)('pg', () => {
101
117
  expect(await directory.ls()).toEqual([]);
102
118
  });
103
119
 
120
+ test('ls prefers dir: row over stale runtime rows', async () => {
121
+ const sql = await getConnection();
122
+ await sql`INSERT INTO agents (id, pane_id, session, repo_path, state, role, started_at, last_state_change, metadata) VALUES ('runtime-1', '%1', 's', '/tmp', 'done', 'ls-dup', now(), now(), '{}')`;
123
+ await sql`INSERT INTO agents (id, role, custom_name, started_at, metadata) VALUES ('dir:ls-dup', 'ls-dup', 'ls-dup', now() - interval '1 hour', '{"dir":"/real/dir","model":"sonnet","description":"Directory entry"}')`;
124
+
125
+ const entries = await directory.ls();
126
+ const entry = entries.find((e) => e.name === 'ls-dup');
127
+ expect(entry).not.toBeNull();
128
+ expect(entry!.model).toBe('sonnet');
129
+ expect(entry!.description).toBe('Directory entry');
130
+ });
131
+
104
132
  test('ls includes metadata fields from PG', async () => {
105
133
  const sql = await getConnection();
106
134
  await sql`INSERT INTO agents (id, role, custom_name, started_at, metadata) VALUES ('dir:ls-meta', 'ls-meta', 'ls-meta', now(), '{"model":"opus","color":"green","provider":"codex","description":"Ls test"}')`;
@@ -163,7 +163,12 @@ export async function resolve(name: string): Promise<ResolvedAgent | null> {
163
163
  try {
164
164
  const { getConnection } = await import('./db.js');
165
165
  const sql = await getConnection();
166
- const rows = await sql`SELECT role, metadata FROM agents WHERE role = ${name} LIMIT 1`;
166
+ const rows = await sql`
167
+ SELECT role, metadata FROM agents
168
+ WHERE role = ${name}
169
+ ORDER BY (CASE WHEN id LIKE 'dir:%' THEN 0 ELSE 1 END), started_at DESC
170
+ LIMIT 1
171
+ `;
167
172
  if (rows.length > 0) {
168
173
  const meta = parseMetadata(rows[0].metadata);
169
174
  return { entry: roleToEntry(name, undefined, meta), builtin: false };
@@ -228,7 +233,7 @@ export async function ls(): Promise<ScopedDirectoryEntry[]> {
228
233
  FROM agents a
229
234
  LEFT JOIN executors e ON a.current_executor_id = e.id
230
235
  WHERE a.role IS NOT NULL
231
- ORDER BY a.role, a.started_at DESC
236
+ ORDER BY a.role, (CASE WHEN a.id LIKE 'dir:%' THEN 0 ELSE 1 END), a.started_at DESC
232
237
  `;
233
238
  for (const row of rows) {
234
239
  const name = row.role as string;