@nex-ai/nex 0.1.27 → 0.1.29

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.
Files changed (46) hide show
  1. package/README.md +13 -8
  2. package/dist/agent/message-router.d.ts +26 -0
  3. package/dist/agent/message-router.js +114 -0
  4. package/dist/agent/message-router.js.map +1 -0
  5. package/dist/agent/orchestrate.d.ts +26 -0
  6. package/dist/agent/orchestrate.js +87 -0
  7. package/dist/agent/orchestrate.js.map +1 -0
  8. package/dist/agent/providers/types.d.ts +5 -0
  9. package/dist/agent/providers/types.js +2 -0
  10. package/dist/agent/providers/types.js.map +1 -0
  11. package/dist/agent/templates.js +7 -0
  12. package/dist/agent/templates.js.map +1 -1
  13. package/dist/agent/tick-manager.d.ts +20 -0
  14. package/dist/agent/tick-manager.js +50 -0
  15. package/dist/agent/tick-manager.js.map +1 -0
  16. package/dist/commands/dispatch.d.ts +5 -0
  17. package/dist/commands/dispatch.js +108 -0
  18. package/dist/commands/dispatch.js.map +1 -1
  19. package/dist/index.js +90 -22
  20. package/dist/index.js.map +1 -1
  21. package/dist/lib/claude-code-installer.d.ts +8 -0
  22. package/dist/lib/claude-code-installer.js +76 -0
  23. package/dist/lib/claude-code-installer.js.map +1 -0
  24. package/dist/plugin/context-format.d.ts +4 -0
  25. package/dist/plugin/context-format.js +4 -0
  26. package/dist/plugin/context-format.js.map +1 -1
  27. package/dist/plugin/recall-filter.d.ts +14 -5
  28. package/dist/plugin/recall-filter.js +29 -41
  29. package/dist/plugin/recall-filter.js.map +1 -1
  30. package/dist/tui/app.js +9 -104
  31. package/dist/tui/app.js.map +1 -1
  32. package/dist/tui/register-views.js +7 -1
  33. package/dist/tui/register-views.js.map +1 -1
  34. package/dist/tui/views/stream.d.ts +16 -0
  35. package/dist/tui/views/stream.js +210 -0
  36. package/dist/tui/views/stream.js.map +1 -0
  37. package/package.json +7 -5
  38. package/platform-rules/aider-conventions.md +20 -12
  39. package/platform-rules/cline-rules.md +20 -8
  40. package/platform-rules/continue-rules.md +20 -8
  41. package/platform-rules/cursor-rules.md +20 -8
  42. package/platform-rules/kilocode-rules.md +20 -8
  43. package/platform-rules/opencode-agents.md +20 -12
  44. package/platform-rules/vscode-instructions.md +20 -12
  45. package/platform-rules/windsurf-rules.md +20 -8
  46. package/platform-rules/zed-rules.md +20 -12
package/README.md CHANGED
@@ -1,9 +1,14 @@
1
- # @nex-ai/nex
1
+ # Nex: Compounding Intelligence for AI agents
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@nex-ai/nex)](https://www.npmjs.com/package/@nex-ai/nex)
4
4
  [![GitHub](https://img.shields.io/badge/github-nex--crm%2Fnex--as--a--skill-blue)](https://github.com/nex-crm/nex-as-a-skill)
5
+ [![Discord](https://img.shields.io/badge/Discord-Join%20Community-5865F2?logo=discord&logoColor=white)](https://discord.gg/gjSySC3PzV)
5
6
 
6
- Nex CLI provides organizational context & memory to AI agents across 12 platforms.
7
+ Turn all your AI agent conversations into a unified knowledge graph. Supports Claude Code, Codex, OpenClaw, Cursor, OpenCode, etc. Adds additional context from Email, Meetings, Slack, HubSpot, Salesforce.
8
+
9
+ <a href="https://discord.gg/gjSySC3PzV"><img src="https://img.shields.io/badge/Join%20our%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Join our Discord" /></a>
10
+
11
+ Talk to the team, share feedback, and connect with other developers building AI agents with Nex.
7
12
 
8
13
  **GitHub**: [github.com/nex-crm/nex-as-a-skill](https://github.com/nex-crm/nex-as-a-skill)
9
14
 
@@ -11,7 +16,7 @@ Nex CLI provides organizational context & memory to AI agents across 12 platform
11
16
 
12
17
  ```bash
13
18
  # Install globally
14
- npm install -g @nex-ai/nex
19
+ bun install -g @nex-ai/nex
15
20
 
16
21
  # Or run directly (no install)
17
22
  npx @nex-ai/nex ask "who is Maria?"
@@ -99,7 +104,7 @@ nex graph # Open the workspace graph in your browser
99
104
  - Creates `.nex.toml` project config with commented defaults
100
105
  - Syncs API key to `~/.nex-mcp.json` (shared config)
101
106
 
102
- **Single install**: `npm install -g @nex-ai/nex` bundles everything — hooks, adapters, platform plugins, slash commands, rules, and MCP server. No separate packages needed.
107
+ **Single install**: `bun install -g @nex-ai/nex` bundles everything — hooks, adapters, platform plugins, slash commands, rules, and MCP server. No separate packages needed.
103
108
 
104
109
  **Integration hierarchy** (per platform): Hooks > Custom plugins > Custom agents/modes > Workflows > Rules > MCP. Each platform gets every layer it supports.
105
110
 
@@ -252,9 +257,9 @@ git diff | nex capture
252
257
  ## Development
253
258
 
254
259
  ```bash
255
- npm install
256
- npm run build # TypeScript → dist/
257
- npm run dev # Run with tsx (no build)
258
- npm test # Unit tests
260
+ bun install
261
+ bun run build # TypeScript → dist/
262
+ bun run dev # Run TS directly (no build)
263
+ bun test # Unit + integration tests
259
264
  NEX_DEV_URL=http://localhost:30000 nex ask "test" # Local API
260
265
  ```
@@ -0,0 +1,26 @@
1
+ /**
2
+ * MessageRouter: routes incoming messages to the best-fit agent
3
+ * using TaskRouter for skill scoring and thread-detection heuristics
4
+ * for follow-up messages.
5
+ */
6
+ export interface RoutingResult {
7
+ primary: string;
8
+ collaborators: string[];
9
+ isFollowUp: boolean;
10
+ teamLeadAware: boolean;
11
+ }
12
+ export declare class MessageRouter {
13
+ private router;
14
+ private recentThreads;
15
+ private FOLLOWUP_WINDOW_MS;
16
+ registerAgent(slug: string, expertise: string[]): void;
17
+ unregisterAgent(slug: string): void;
18
+ recordAgentActivity(agentSlug: string): void;
19
+ route(message: string, availableAgents: Array<{
20
+ slug: string;
21
+ expertise: string[];
22
+ }>): RoutingResult;
23
+ private detectFollowUp;
24
+ /** Extract skill keywords from a message using pattern matching. */
25
+ extractSkills(message: string): string[];
26
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * MessageRouter: routes incoming messages to the best-fit agent
3
+ * using TaskRouter for skill scoring and thread-detection heuristics
4
+ * for follow-up messages.
5
+ */
6
+ import { TaskRouter } from '../orchestration/router.js';
7
+ export class MessageRouter {
8
+ router = new TaskRouter();
9
+ recentThreads = new Map();
10
+ FOLLOWUP_WINDOW_MS = 30_000;
11
+ registerAgent(slug, expertise) {
12
+ this.router.registerAgent(slug, expertise.map((e) => ({ name: e, description: e, proficiency: 0.8 })));
13
+ }
14
+ unregisterAgent(slug) {
15
+ this.router.unregisterAgent(slug);
16
+ }
17
+ recordAgentActivity(agentSlug) {
18
+ this.recentThreads.set(agentSlug, { agentSlug, lastActivity: Date.now() });
19
+ }
20
+ route(message, availableAgents) {
21
+ // Step 1: Thread detection
22
+ const followUp = this.detectFollowUp(message);
23
+ if (followUp) {
24
+ return { primary: followUp, collaborators: [], isFollowUp: true, teamLeadAware: false };
25
+ }
26
+ // Step 2: Extract skills from message using keyword patterns
27
+ const requiredSkills = this.extractSkills(message);
28
+ // No recognized skills → route to team-lead for triage
29
+ if (requiredSkills.length === 0) {
30
+ return { primary: 'team-lead', collaborators: [], isFollowUp: false, teamLeadAware: false };
31
+ }
32
+ // Step 3: Register agents (ensure current state) and score
33
+ for (const agent of availableAgents) {
34
+ this.registerAgent(agent.slug, agent.expertise);
35
+ }
36
+ const task = {
37
+ id: `route-${Date.now()}`,
38
+ title: message,
39
+ description: message,
40
+ requiredSkills,
41
+ priority: 'medium',
42
+ status: 'pending',
43
+ createdAt: Date.now(),
44
+ };
45
+ const capable = this.router.findCapableAgents(task);
46
+ if (capable.length === 0) {
47
+ return { primary: 'team-lead', collaborators: [], isFollowUp: false, teamLeadAware: false };
48
+ }
49
+ const primary = capable[0].agentSlug;
50
+ const collaborators = capable
51
+ .slice(1)
52
+ .filter((c) => c.score > 0.5)
53
+ .map((c) => c.agentSlug);
54
+ return {
55
+ primary,
56
+ collaborators,
57
+ isFollowUp: false,
58
+ teamLeadAware: primary !== 'team-lead',
59
+ };
60
+ }
61
+ detectFollowUp(message) {
62
+ const now = Date.now();
63
+ const followUpPatterns = /^(also|and |too |that |it |the results|those |these |this |what about|how about|can you also)/i;
64
+ if (!followUpPatterns.test(message.trim()))
65
+ return null;
66
+ // Find most recent active thread within window
67
+ let mostRecent = null;
68
+ for (const [, ctx] of this.recentThreads) {
69
+ if (now - ctx.lastActivity <= this.FOLLOWUP_WINDOW_MS) {
70
+ if (!mostRecent || ctx.lastActivity > mostRecent.lastActivity) {
71
+ mostRecent = ctx;
72
+ }
73
+ }
74
+ }
75
+ return mostRecent?.agentSlug ?? null;
76
+ }
77
+ /** Extract skill keywords from a message using pattern matching. */
78
+ extractSkills(message) {
79
+ const skillPatterns = [
80
+ {
81
+ pattern: /\b(research|investigate|analyze|analysis|competitor)\b/i,
82
+ skills: ['market-research', 'competitive-analysis'],
83
+ },
84
+ {
85
+ pattern: /\b(leads?|prospect|outreach|prospecting|pipeline)\b/i,
86
+ skills: ['prospecting', 'outreach'],
87
+ },
88
+ {
89
+ pattern: /\b(enrich|validate|data quality|fill in|complete)\b/i,
90
+ skills: ['data-enrichment', 'validation'],
91
+ },
92
+ {
93
+ pattern: /\b(seo|keyword|search visibility|ranking|content)\b/i,
94
+ skills: ['seo', 'content-analysis'],
95
+ },
96
+ {
97
+ pattern: /\b(customer|success|health|renewal|churn|retention)\b/i,
98
+ skills: ['relationship-management', 'health-scoring'],
99
+ },
100
+ {
101
+ pattern: /\b(code|bug|fix|implement|build|deploy|test)\b/i,
102
+ skills: ['general', 'planning'],
103
+ },
104
+ ];
105
+ const skills = [];
106
+ for (const { pattern, skills: matchedSkills } of skillPatterns) {
107
+ if (pattern.test(message)) {
108
+ skills.push(...matchedSkills);
109
+ }
110
+ }
111
+ return [...new Set(skills)];
112
+ }
113
+ }
114
+ //# sourceMappingURL=message-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-router.js","sourceRoot":"","sources":["../../src/agent/message-router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Objective-first delegation: Team-Lead decomposes objectives
3
+ * and routes sub-tasks to specialist agents via steer messages.
4
+ *
5
+ * Uses the orchestration TaskRouter for skill matching and
6
+ * MessageQueues for non-blocking delegation.
7
+ */
8
+ import type { AgentConfig } from './types.js';
9
+ import type { MessageQueues } from './queues.js';
10
+ /** Extract actionable sub-tasks from the Team-Lead's response text. */
11
+ export declare function extractSubTasks(response: string): Array<{
12
+ action: string;
13
+ skills: string[];
14
+ }>;
15
+ export interface Specialist {
16
+ config: AgentConfig;
17
+ }
18
+ /**
19
+ * Delegate sub-tasks to specialist agents.
20
+ * Non-blocking: sends steer messages to matching specialists
21
+ * so they pick up work on their next tick.
22
+ */
23
+ export declare function delegateToSpecialists(response: string, specialists: Specialist[], queues: MessageQueues): Array<{
24
+ agentSlug: string;
25
+ task: string;
26
+ }>;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Objective-first delegation: Team-Lead decomposes objectives
3
+ * and routes sub-tasks to specialist agents via steer messages.
4
+ *
5
+ * Uses the orchestration TaskRouter for skill matching and
6
+ * MessageQueues for non-blocking delegation.
7
+ */
8
+ import { TaskRouter } from '../orchestration/router.js';
9
+ import { getAgentService } from '../tui/services/agent-service.js';
10
+ /** Map agent expertise strings to SkillDeclarations for the router. */
11
+ function expertiseToSkills(expertise) {
12
+ return expertise.map(e => ({
13
+ name: e,
14
+ description: e,
15
+ proficiency: 0.8,
16
+ }));
17
+ }
18
+ /** Extract actionable sub-tasks from the Team-Lead's response text. */
19
+ export function extractSubTasks(response) {
20
+ const subTasks = [];
21
+ // Skill keyword mapping — matches action words to required expertise
22
+ const skillPatterns = [
23
+ { pattern: /\b(research|investigate|analyze|analysis)\b/i, skills: ['research', 'market-research', 'competitive-analysis'] },
24
+ { pattern: /\b(find leads|generate leads|prospect|outreach|prospecting)\b/i, skills: ['prospecting', 'enrichment', 'outreach'] },
25
+ { pattern: /\b(enrich|validate|fill in|complete data|data quality)\b/i, skills: ['data-enrichment', 'research', 'validation'] },
26
+ { pattern: /\b(seo|keyword|search visibility|content.?analysis|ranking)\b/i, skills: ['seo', 'content-analysis', 'keyword-research'] },
27
+ { pattern: /\b(customer success|health score|renewal|churn|retention)\b/i, skills: ['relationship-management', 'health-scoring', 'renewal-tracking'] },
28
+ ];
29
+ // Split response into sentences and look for action-oriented ones
30
+ const sentences = response.split(/[.!?\n]+/).map(s => s.trim()).filter(Boolean);
31
+ for (const sentence of sentences) {
32
+ for (const { pattern, skills } of skillPatterns) {
33
+ if (pattern.test(sentence)) {
34
+ // Avoid duplicates
35
+ if (!subTasks.some(t => t.action === sentence)) {
36
+ subTasks.push({ action: sentence, skills });
37
+ }
38
+ }
39
+ }
40
+ }
41
+ return subTasks;
42
+ }
43
+ /**
44
+ * Delegate sub-tasks to specialist agents.
45
+ * Non-blocking: sends steer messages to matching specialists
46
+ * so they pick up work on their next tick.
47
+ */
48
+ export function delegateToSpecialists(response, specialists, queues) {
49
+ if (specialists.length === 0)
50
+ return [];
51
+ const subTasks = extractSubTasks(response);
52
+ if (subTasks.length === 0)
53
+ return [];
54
+ // Build a router with registered specialists
55
+ const router = new TaskRouter();
56
+ for (const spec of specialists) {
57
+ router.registerAgent(spec.config.slug, expertiseToSkills(spec.config.expertise));
58
+ }
59
+ const delegated = [];
60
+ for (const subTask of subTasks) {
61
+ const taskDef = {
62
+ id: `delegate-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
63
+ title: subTask.action,
64
+ description: subTask.action,
65
+ requiredSkills: subTask.skills,
66
+ priority: 'medium',
67
+ status: 'pending',
68
+ createdAt: Date.now(),
69
+ };
70
+ const best = router.findBestAgent(taskDef);
71
+ if (best && best.score > 0.1) {
72
+ // Steer the specialist with the sub-task
73
+ queues.steer(best.agentSlug, `[TEAM-LEAD DELEGATION] ${subTask.action}`);
74
+ delegated.push({ agentSlug: best.agentSlug, task: subTask.action });
75
+ // Ensure specialist agent tick loop is running
76
+ try {
77
+ const agentService = getAgentService();
78
+ agentService.ensureRunning(best.agentSlug);
79
+ }
80
+ catch {
81
+ // AgentService may not be initialized yet — delegation still succeeds via queue
82
+ }
83
+ }
84
+ }
85
+ return delegated;
86
+ }
87
+ //# sourceMappingURL=orchestrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrate.js","sourceRoot":"","sources":["../../src/agent/orchestrate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export type LLMProvider = 'gemini' | 'claude-code';
2
+ export interface ProviderConfig {
3
+ provider: LLMProvider;
4
+ geminiApiKey?: string;
5
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/agent/providers/types.ts"],"names":[],"mappings":""}
@@ -37,6 +37,13 @@ export const templates = {
37
37
  heartbeatCron: '0 */8 * * *',
38
38
  tools: ['nex_search', 'nex_ask', 'nex_record_list', 'nex_record_get', 'nex_remember'],
39
39
  },
40
+ 'team-lead': {
41
+ name: 'Team Lead',
42
+ expertise: ['general', 'research', 'analysis', 'communication', 'planning', 'orchestration'],
43
+ personality: 'You are the Team Lead — the primary interface between the human and the specialist agents. You understand objectives, break them into tasks, delegate to the right specialists, and synthesize results. When the user says what they want, you figure out the path to get there using the context graph and available agents. You always respond first, then coordinate behind the scenes.',
44
+ heartbeatCron: 'manual',
45
+ tools: ['nex_search', 'nex_ask', 'nex_remember', 'nex_record_list', 'nex_record_get', 'nex_record_create', 'nex_record_update'],
46
+ },
40
47
  'founding-agent': {
41
48
  name: 'Founding Agent',
42
49
  expertise: ['general', 'research', 'analysis', 'communication', 'planning'],
@@ -1 +1 @@
1
- {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/agent/templates.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,CAAC,MAAM,SAAS,GAA8C;IAClE,WAAW,EAAE;QACX,IAAI,EAAE,aAAa;QACnB,SAAS,EAAE,CAAC,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,CAAC;QAC1D,WAAW,EAAE,2DAA2D;QACxE,aAAa,EAAE,OAAO;QACtB,KAAK,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,iBAAiB,CAAC;KACpE;IACD,UAAU,EAAE;QACV,IAAI,EAAE,gBAAgB;QACtB,SAAS,EAAE,CAAC,aAAa,EAAE,YAAY,EAAE,UAAU,CAAC;QACpD,WAAW,EAAE,mDAAmD;QAChE,aAAa,EAAE,aAAa;QAC5B,KAAK,EAAE,CAAC,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,cAAc,CAAC;KAC9E;IACD,YAAY,EAAE;QACZ,IAAI,EAAE,eAAe;QACrB,SAAS,EAAE,CAAC,iBAAiB,EAAE,UAAU,EAAE,YAAY,CAAC;QACxD,WAAW,EAAE,6CAA6C;QAC1D,aAAa,EAAE,aAAa;QAC5B,KAAK,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,cAAc,CAAC;KACxF;IACD,UAAU,EAAE;QACV,IAAI,EAAE,kBAAkB;QACxB,SAAS,EAAE,CAAC,iBAAiB,EAAE,sBAAsB,EAAE,gBAAgB,CAAC;QACxE,WAAW,EAAE,oDAAoD;QACjE,aAAa,EAAE,OAAO;QACtB,KAAK,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,cAAc,CAAC;KACjD;IACD,kBAAkB,EAAE;QAClB,IAAI,EAAE,kBAAkB;QACxB,SAAS,EAAE,CAAC,yBAAyB,EAAE,gBAAgB,EAAE,kBAAkB,CAAC;QAC5E,WAAW,EAAE,uDAAuD;QACpE,aAAa,EAAE,aAAa;QAC5B,KAAK,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,CAAC;KACtF;IACD,gBAAgB,EAAE;QAChB,IAAI,EAAE,gBAAgB;QACtB,SAAS,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,CAAC;QAC3E,WAAW,EAAE,uIAAuI;QACpJ,aAAa,EAAE,OAAO;QACtB,KAAK,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,mBAAmB,CAAC;KAChI;CACF,CAAC"}
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/agent/templates.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,CAAC,MAAM,SAAS,GAA8C;IAClE,WAAW,EAAE;QACX,IAAI,EAAE,aAAa;QACnB,SAAS,EAAE,CAAC,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,CAAC;QAC1D,WAAW,EAAE,2DAA2D;QACxE,aAAa,EAAE,OAAO;QACtB,KAAK,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,iBAAiB,CAAC;KACpE;IACD,UAAU,EAAE;QACV,IAAI,EAAE,gBAAgB;QACtB,SAAS,EAAE,CAAC,aAAa,EAAE,YAAY,EAAE,UAAU,CAAC;QACpD,WAAW,EAAE,mDAAmD;QAChE,aAAa,EAAE,aAAa;QAC5B,KAAK,EAAE,CAAC,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,cAAc,CAAC;KAC9E;IACD,YAAY,EAAE;QACZ,IAAI,EAAE,eAAe;QACrB,SAAS,EAAE,CAAC,iBAAiB,EAAE,UAAU,EAAE,YAAY,CAAC;QACxD,WAAW,EAAE,6CAA6C;QAC1D,aAAa,EAAE,aAAa;QAC5B,KAAK,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,cAAc,CAAC;KACxF;IACD,UAAU,EAAE;QACV,IAAI,EAAE,kBAAkB;QACxB,SAAS,EAAE,CAAC,iBAAiB,EAAE,sBAAsB,EAAE,gBAAgB,CAAC;QACxE,WAAW,EAAE,oDAAoD;QACjE,aAAa,EAAE,OAAO;QACtB,KAAK,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,cAAc,CAAC;KACjD;IACD,kBAAkB,EAAE;QAClB,IAAI,EAAE,kBAAkB;QACxB,SAAS,EAAE,CAAC,yBAAyB,EAAE,gBAAgB,EAAE,kBAAkB,CAAC;QAC5E,WAAW,EAAE,uDAAuD;QACpE,aAAa,EAAE,aAAa;QAC5B,KAAK,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,CAAC;KACtF;IACD,WAAW,EAAE;QACX,IAAI,EAAE,WAAW;QACjB,SAAS,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,eAAe,CAAC;QAC5F,WAAW,EAAE,4XAA4X;QACzY,aAAa,EAAE,QAAQ;QACvB,KAAK,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,mBAAmB,CAAC;KAChI;IACD,gBAAgB,EAAE;QAChB,IAAI,EAAE,gBAAgB;QACtB,SAAS,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,CAAC;QAC3E,WAAW,EAAE,uIAAuI;QACpJ,aAAa,EAAE,OAAO;QACtB,KAAK,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,mBAAmB,CAAC;KAChI;CACF,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * TickManager: drives agent loops on a fixed interval.
3
+ * Each agent gets its own setInterval; ticks are skipped
4
+ * when the agent is idle with no queued work.
5
+ */
6
+ export declare class TickManager {
7
+ private intervals;
8
+ private tickRateMs;
9
+ constructor(tickRateMs?: number);
10
+ startLoop(slug: string, loop: {
11
+ getState(): {
12
+ phase: string;
13
+ };
14
+ start(): void;
15
+ tick(): Promise<void>;
16
+ }, hasWork: () => boolean): void;
17
+ stopLoop(slug: string): void;
18
+ isRunning(slug: string): boolean;
19
+ stopAll(): void;
20
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * TickManager: drives agent loops on a fixed interval.
3
+ * Each agent gets its own setInterval; ticks are skipped
4
+ * when the agent is idle with no queued work.
5
+ */
6
+ export class TickManager {
7
+ intervals = new Map();
8
+ tickRateMs;
9
+ constructor(tickRateMs = 500) {
10
+ this.tickRateMs = tickRateMs;
11
+ }
12
+ startLoop(slug, loop, hasWork) {
13
+ if (this.intervals.has(slug))
14
+ return; // idempotent
15
+ const interval = setInterval(async () => {
16
+ const state = loop.getState();
17
+ // Only tick if there's pending work
18
+ if ((state.phase === 'done' || state.phase === 'error' || state.phase === 'idle') &&
19
+ !hasWork()) {
20
+ return; // idle skip
21
+ }
22
+ if (state.phase === 'done' || state.phase === 'error') {
23
+ loop.start(); // reset to idle
24
+ }
25
+ try {
26
+ await loop.tick();
27
+ }
28
+ catch {
29
+ /* swallow tick errors */
30
+ }
31
+ }, this.tickRateMs);
32
+ this.intervals.set(slug, interval);
33
+ }
34
+ stopLoop(slug) {
35
+ const interval = this.intervals.get(slug);
36
+ if (interval) {
37
+ clearInterval(interval);
38
+ this.intervals.delete(slug);
39
+ }
40
+ }
41
+ isRunning(slug) {
42
+ return this.intervals.has(slug);
43
+ }
44
+ stopAll() {
45
+ for (const [slug] of this.intervals) {
46
+ this.stopLoop(slug);
47
+ }
48
+ }
49
+ }
50
+ //# sourceMappingURL=tick-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tick-manager.js","sourceRoot":"","sources":["../../src/agent/tick-manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,OAAO,WAAW;IACd,SAAS,GAAG,IAAI,GAAG,EAA0C,CAAC;IAC9D,UAAU,CAAS;IAE3B,YAAY,UAAU,GAAG,GAAG;QAC1B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED,SAAS,CACP,IAAY,EACZ,IAA6E,EAC7E,OAAsB;QAEtB,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,aAAa;QAEnD,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;YACtC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC9B,oCAAoC;YACpC,IACE,CAAC,KAAK,CAAC,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC,KAAK,KAAK,OAAO,IAAI,KAAK,CAAC,KAAK,KAAK,MAAM,CAAC;gBAC7E,CAAC,OAAO,EAAE,EACV,CAAC;gBACD,OAAO,CAAC,YAAY;YACtB,CAAC;YACD,IAAI,KAAK,CAAC,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;gBACtD,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,gBAAgB;YAChC,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YACpB,CAAC;YAAC,MAAM,CAAC;gBACP,yBAAyB;YAC3B,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAEpB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED,QAAQ,CAAC,IAAY;QACnB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,QAAQ,EAAE,CAAC;YACb,aAAa,CAAC,QAAQ,CAAC,CAAC;YACxB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,SAAS,CAAC,IAAY;QACpB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,OAAO;QACL,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACpC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;CACF"}
@@ -28,6 +28,11 @@ export interface CommandContext {
28
28
  * The TUI and CLI can both call this to execute commands without stdout side effects.
29
29
  */
30
30
  export declare function dispatch(input: string, ctx?: CommandContext): Promise<CommandResult>;
31
+ /**
32
+ * Dispatch from pre-tokenized args (e.g. process.argv).
33
+ * Use this when the shell has already handled quoting/splitting.
34
+ */
35
+ export declare function dispatchTokens(tokens: string[], ctx?: CommandContext): Promise<CommandResult>;
31
36
  /** All registered command names, for autocomplete. */
32
37
  export declare const commandNames: string[];
33
38
  /** Help entries for each command, with category and description. */
@@ -1217,6 +1217,106 @@ register("agent stop", { execute: executeAgentStop, description: "Stop an agent
1217
1217
  register("agent steer", { execute: executeAgentSteer, description: "Send a steer message to an agent", category: "agent", usage: "agent steer <slug> <message>" });
1218
1218
  register("agent inspect", { execute: executeAgentInspect, description: "Inspect agent config and state", category: "agent", usage: "agent inspect <slug>" });
1219
1219
  register("agent templates", { execute: executeAgentTemplates, description: "List available agent templates", category: "agent", usage: "agent templates" });
1220
+ // -- Scan --
1221
+ async function executeScan(args, ctx) {
1222
+ const { positional, opts } = extractOpts(args);
1223
+ const dir = positional[0] || ".";
1224
+ const maxFiles = typeof opts["max-files"] === "string" ? parseInt(opts["max-files"], 10) : undefined;
1225
+ if (maxFiles !== undefined && (!Number.isInteger(maxFiles) || maxFiles <= 0)) {
1226
+ return fail("Invalid --max-files. Expected a positive integer.");
1227
+ }
1228
+ const depth = typeof opts.depth === "string" ? parseInt(opts.depth, 10) : undefined;
1229
+ if (depth !== undefined && (!Number.isInteger(depth) || depth < 0)) {
1230
+ return fail("Invalid --depth. Expected a non-negative integer.");
1231
+ }
1232
+ const extensions = typeof opts.extensions === "string" ? opts.extensions : undefined;
1233
+ const force = opts.force === true;
1234
+ const dryRun = opts["dry-run"] === true;
1235
+ try {
1236
+ const { scanFiles, loadScanConfig } = await import("../lib/file-scanner.js");
1237
+ const scanOpts = loadScanConfig({
1238
+ extensions: extensions?.split(",").map((e) => e.trim()),
1239
+ maxFiles,
1240
+ depth,
1241
+ force,
1242
+ dryRun,
1243
+ });
1244
+ const client = makeClient(ctx);
1245
+ const result = await scanFiles(dir, scanOpts, async (content, context) => {
1246
+ return client.post("/v1/context/text", { content, context });
1247
+ });
1248
+ return ok({ scanned: result.scanned, skipped: result.skipped, errors: result.errors }, ctx);
1249
+ }
1250
+ catch (err) {
1251
+ return wrapError(err);
1252
+ }
1253
+ }
1254
+ register("scan", { execute: executeScan, description: "Scan directory and ingest files", category: "config", usage: "scan [dir] [--max-files <n>] [--force] [--dry-run]" });
1255
+ // -- Register --
1256
+ async function executeRegister(args, ctx) {
1257
+ const { opts } = extractOpts(args);
1258
+ const email = typeof opts.email === "string" ? opts.email : undefined;
1259
+ if (!email)
1260
+ return fail("Usage: register --email <email>");
1261
+ try {
1262
+ const { persistRegistration } = await import("../lib/config.js");
1263
+ const client = new NexClient(undefined, ctx.timeout ?? resolveTimeout());
1264
+ const data = await client.register(email, typeof opts.name === "string" ? opts.name : undefined, typeof opts.company === "string" ? opts.company : undefined);
1265
+ persistRegistration(data);
1266
+ return ok(data, ctx);
1267
+ }
1268
+ catch (err) {
1269
+ return wrapError(err);
1270
+ }
1271
+ }
1272
+ register("register", { execute: executeRegister, description: "Register a new Nex workspace", category: "config", usage: "register --email <email> [--name <name>] [--company <company>]" });
1273
+ // -- Session --
1274
+ async function executeSessionList(_args, ctx) {
1275
+ try {
1276
+ const { SessionStore } = await import("../lib/session-store.js");
1277
+ const store = new SessionStore();
1278
+ return ok(store.list(), ctx);
1279
+ }
1280
+ catch (err) {
1281
+ return wrapError(err);
1282
+ }
1283
+ }
1284
+ async function executeSessionClear(_args, ctx) {
1285
+ try {
1286
+ const { SessionStore } = await import("../lib/session-store.js");
1287
+ const store = new SessionStore();
1288
+ store.clear();
1289
+ return ok({ message: "All sessions cleared." }, ctx);
1290
+ }
1291
+ catch (err) {
1292
+ return wrapError(err);
1293
+ }
1294
+ }
1295
+ register("session list", { execute: executeSessionList, description: "List stored session mappings", category: "config" });
1296
+ register("session clear", { execute: executeSessionClear, description: "Clear all session mappings", category: "config" });
1297
+ // -- List member operations --
1298
+ function makeListRecordCommand(method, usage, pathFn, bodyFn) {
1299
+ return async (args, ctx) => {
1300
+ const { positional, opts } = extractOpts(args);
1301
+ const [listId, recordId] = positional;
1302
+ if (!listId || !recordId)
1303
+ return fail(`Usage: ${usage}`);
1304
+ try {
1305
+ const client = makeClient(ctx);
1306
+ const path = pathFn(encodeURIComponent(listId), encodeURIComponent(recordId));
1307
+ const body = bodyFn?.(recordId, opts);
1308
+ const data = method === "delete" ? await client.delete(path) : await client[method](path, body);
1309
+ return ok(data, ctx);
1310
+ }
1311
+ catch (err) {
1312
+ return wrapError(err);
1313
+ }
1314
+ };
1315
+ }
1316
+ register("list add-member", { execute: makeListRecordCommand("post", "list add-member <list-id> <record-id>", (lid) => `/v1/lists/${lid}/records`, (rid) => ({ record_id: rid })), description: "Add a record to a list", category: "write", usage: "list add-member <list-id> <record-id>" });
1317
+ register("list upsert-member", { execute: makeListRecordCommand("put", "list upsert-member <list-id> <record-id>", (lid, rid) => `/v1/lists/${lid}/records/${rid}`, (_rid, opts) => opts), description: "Upsert a record in a list", category: "write", usage: "list upsert-member <list-id> <record-id>" });
1318
+ register("list update-record", { execute: makeListRecordCommand("patch", "list update-record <list-id> <record-id>", (lid, rid) => `/v1/lists/${lid}/records/${rid}`, (_rid, opts) => opts), description: "Update a list record", category: "write", usage: "list update-record <list-id> <record-id>" });
1319
+ register("list remove-record", { execute: makeListRecordCommand("delete", "list remove-record <list-id> <record-id>", (lid, rid) => `/v1/lists/${lid}/records/${rid}`), description: "Remove a record from a list", category: "write", usage: "list remove-record <list-id> <record-id>" });
1220
1320
  // -- TUI view hints (so aliases resolve and dispatch does not return "unknown command") --
1221
1321
  register("chat", {
1222
1322
  execute: async () => ({ output: "Use the 'c' keybinding to open the chat view.", exitCode: 0 }),
@@ -1241,6 +1341,7 @@ const COMMAND_ALIASES = {
1241
1341
  objects: "object list",
1242
1342
  orch: "orchestration",
1243
1343
  setup: "init",
1344
+ sessions: "session list",
1244
1345
  };
1245
1346
  /**
1246
1347
  * Resolve alias tokens into their canonical form.
@@ -1285,6 +1386,13 @@ function resolveCommand(tokens) {
1285
1386
  */
1286
1387
  export async function dispatch(input, ctx) {
1287
1388
  const tokens = parseInput(input);
1389
+ return dispatchTokens(tokens, ctx);
1390
+ }
1391
+ /**
1392
+ * Dispatch from pre-tokenized args (e.g. process.argv).
1393
+ * Use this when the shell has already handled quoting/splitting.
1394
+ */
1395
+ export async function dispatchTokens(tokens, ctx) {
1288
1396
  if (tokens.length === 0) {
1289
1397
  return fail("No command provided.");
1290
1398
  }